// This is a part of the Active Template Library.
// Copyright (C) Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Active Template Library Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Active Template Library product.

#ifndef __ATLSTENCIL_H__
#define __ATLSTENCIL_H__

#pragma once

#include <atlisapi.h>
#include <atlfile.h>
#include <atlutil.h>
#include <math.h>

#ifdef ATL_DEBUG_STENCILS
#include <atlsrvres.h>

#ifndef ATL_STENCIL_MAX_ERROR_LEN
#define ATL_STENCIL_MAX_ERROR_LEN 256
#endif
#endif // ATL_DEBUG_STENCILS

#ifndef ATL_NO_MLANG
#include <mlang.h>
#endif

#ifndef _ATL_NO_DEFAULT_LIBS
#pragma comment(lib, "shlwapi.lib")
#endif // !_ATL_NO_DEFAULT_LIBS

#pragma warning( push )
#pragma warning(disable: 4127) // conditional expression is constant
#pragma warning(disable: 4511) // copy constructor could not be generated
#pragma warning(disable: 4512) // assignment operator could not be generated
#pragma warning(disable: 4702) // assignment operator could not be generated
#pragma warning(disable: 4625) // copy constructor could not be generated because a base class copy constructor is inaccessible
#pragma warning(disable: 4626) // assignment operator could not be generated because a base class assignment operator is inaccessible
#pragma warning(disable: 4191) // unsafe conversion from 'functionptr1' to 'functionptr2'


#pragma pack(push,_ATL_PACKING)
namespace ATL
{

// Token types
// These tags are token tags for the standard tag replacer implementation
extern __declspec(selectany) const DWORD STENCIL_TEXTTAG             = 0x00000000;
extern __declspec(selectany) const DWORD STENCIL_REPLACEMENT         = 0x00000001;
extern __declspec(selectany) const DWORD STENCIL_ITERATORSTART       = 0x00000002;
extern __declspec(selectany) const DWORD STENCIL_ITERATOREND         = 0x00000003;
extern __declspec(selectany) const DWORD STENCIL_CONDITIONALSTART    = 0x00000004;
extern __declspec(selectany) const DWORD STENCIL_CONDITIONALELSE     = 0x00000005;
extern __declspec(selectany) const DWORD STENCIL_CONDITIONALEND      = 0x00000006;
extern __declspec(selectany) const DWORD STENCIL_STENCILINCLUDE      = 0x00000007;
extern __declspec(selectany) const DWORD STENCIL_STATICINCLUDE       = 0x00000008;
extern __declspec(selectany) const DWORD STENCIL_LOCALE              = 0x00000009;
extern __declspec(selectany) const DWORD STENCIL_CODEPAGE            = 0x0000000a;

// The base for user defined token types
extern __declspec(selectany) const DWORD STENCIL_USER_TOKEN_BASE     = 0x00001000;

// Symbols to use in error handling in the stencil processor
#define STENCIL_INVALIDINDEX            0xFFFFFFFF
#define STENCIL_INVALIDOFFSET           0xFFFFFFFF

// error codes
#define STENCIL_SUCCESS     HTTP_SUCCESS
#define STENCL_FAIL         HTTP_FAIL

#define STENCIL_BASIC_MAP 0
#define STENCIL_ATTR_MAP 1

#ifndef ATL_MAX_METHOD_NAME_LEN
#define ATL_MAX_METHOD_NAME_LEN 64
#endif

#ifndef ATL_MAX_BLOCK_STACK
#define ATL_MAX_BLOCK_STACK 128
#endif

template <class TBase, typename T>
struct CTagReplacerMethodsEx
{
    typedef HTTP_CODE(TBase::*REPLACE_FUNC)();
    typedef HTTP_CODE(TBase::*REPLACE_FUNC_EX)(T*);
    typedef HTTP_CODE(TBase::*PARSE_FUNC)(IAtlMemMgr *, LPCSTR, T**);
    typedef HTTP_CODE(TBase::*REPLACE_FUNC_EX_V)(void *);
    typedef HTTP_CODE(TBase::*PARSE_FUNC_V)(IAtlMemMgr *, LPCSTR, void**);

    static REPLACE_FUNC_EX_V CheckRepl(REPLACE_FUNC p) throw()
    {
        return (REPLACE_FUNC_EX_V) p;
    }

    static REPLACE_FUNC_EX_V CheckReplEx(REPLACE_FUNC_EX p) throw()
    {
        return (REPLACE_FUNC_EX_V) p;
    }

    static PARSE_FUNC_V CheckParse(PARSE_FUNC p) throw()
    {
        return (PARSE_FUNC_V) p;
    }
};

template <class TBase>
struct CTagReplacerMethods
{
    union
    {
        HTTP_CODE(TBase::*pfnMethodEx)(void *);
        HTTP_CODE(TBase::*pfnMethod)();
    };
    HTTP_CODE(TBase::*pfnParse)(IAtlMemMgr *pMemMgr, LPCSTR, void **);
};

#define REPLACEMENT_ENTRY_DEFAULT   0
#define REPLACEMENT_ENTRY_ARGS      1

template <class TBase>
struct CTagReplacerMethodEntry
{
    int nType;  // REPLACEMENT_ENTRY_*
    LPCSTR szMethodName;
    CTagReplacerMethods<TBase> Methods;
};


#define BEGIN_REPLACEMENT_METHOD_MAP(className)\
public:\
	void GetReplacementMethodMap(const ATL::CTagReplacerMethodEntry<className> ** ppOut) const\
	{\
		typedef className __className;\
		static const ATL::CTagReplacerMethodEntry<className> methods[] = {

#define REPLACEMENT_METHOD_ENTRY(methodName, methodFunc)\
	{ 0, methodName,  {  ATL::CTagReplacerMethodsEx<__className, void>::CheckRepl(&__className::methodFunc), NULL }   },

#define REPLACEMENT_METHOD_ENTRY_EX(methodName, methodFunc, paramType, parseFunc)\
	{ 1, methodName,  { ATL::CTagReplacerMethodsEx<__className, paramType>::CheckReplEx(&__className::methodFunc), ATL::CTagReplacerMethodsEx<__className, paramType>::CheckParse(&__className::parseFunc) } },

#define REPLACEMENT_METHOD_ENTRY_EX_STR(methodName, methodFunc) \
	{ 1, methodName,  { ATL::CTagReplacerMethodsEx<__className, char>::CheckReplEx(&__className::methodFunc), ATL::CTagReplacerMethodsEx<__className, char>::CheckParse(&__className::DefaultParseString) } },

#define END_REPLACEMENT_METHOD_MAP()\
		{ 0, NULL, NULL } };\
		*ppOut = methods;\
	}

#define BEGIN_ATTR_REPLACEMENT_METHOD_MAP(className)\
public:\
	void GetAttrReplacementMethodMap(const CTagReplacerMethodEntry<className> ** ppOut) const\
	{\
		typedef className __className;\
		static const ATL::CTagReplacerMethodEntry<className> methods[] = {

#define END_ATTR_REPLACEMENT_METHOD_MAP()\
		{ NULL, NULL, NULL } };\
		*ppOut = methods;\
	}

template <class T>
class ITagReplacerImpl : public ITagReplacer
{
protected:
    IWriteStream *m_pStream;

public:
    typedef HTTP_CODE(T::*REPLACEMENT_METHOD)();
    typedef HTTP_CODE(T::*REPLACEMENT_METHOD_EX)(void *pvParam);

    ITagReplacerImpl() throw()
        : m_pStream(NULL)
    {
    }

    IWriteStream *SetStream(IWriteStream *pStream)
    {
        IWriteStream *pRetStream = m_pStream;
        m_pStream = pStream;
        return pRetStream;
    }

    // Looks up the replacement method offset. Optionally, it will
    // look up the replacement method and object offset of an alternate
    // tag replacer.
    HTTP_CODE FindReplacementOffset(
        LPCSTR szMethodName,
        DWORD *pdwMethodOffset,
        LPCSTR szHandlerName,
        DWORD *pdwHandlerOffset,
        DWORD *pdwMap, void **ppvParam, IAtlMemMgr *pMemMgr)
    {
        ATLENSURE(szMethodName != NULL);
        ATLENSURE(pdwMethodOffset != NULL);
        ATLENSURE((szHandlerName == NULL && pdwHandlerOffset == NULL) ||
                  (szHandlerName != NULL && pdwHandlerOffset != NULL));
        ATLENSURE(pdwMap != NULL);
        ATLENSURE(ppvParam != NULL);
        ATLENSURE(pMemMgr != NULL);

        // we at least have to be looking up a method offset
        if(!pdwMethodOffset || !szMethodName)
            return AtlsHttpError(500, ISE_SUBERR_UNEXPECTED);

        *pdwMethodOffset = STENCIL_INVALIDOFFSET;
        HTTP_CODE hcErr = HTTP_FAIL;
        T *pT = static_cast<T *>(this);

        char szName[ATL_MAX_METHOD_NAME_LEN+1];

        // if a handler name was supplied, we will try to
        // find a different object to handle the method
        if(szHandlerName && *szHandlerName)
        {
            if(!pdwHandlerOffset)
                return AtlsHttpError(500, ISE_SUBERR_UNEXPECTED);

            hcErr = pT->GetHandlerOffset(szHandlerName, pdwHandlerOffset);
            // got the alternate handler, now look up the method offset on
            // the handler.
            if(!hcErr)
            {
                CComPtr<ITagReplacer> spAltTagReplacer;
                hcErr = pT->GetReplacementObject(*pdwHandlerOffset, &spAltTagReplacer);
                if(!hcErr)
                    hcErr = spAltTagReplacer->FindReplacementOffset(szMethodName, pdwMethodOffset,
                            NULL, NULL, pdwMap, ppvParam, pMemMgr);
                return hcErr;
            }
            else
                return hcErr;
        }

        if(!SafeStringCopy(szName, szMethodName))
        {
            return AtlsHttpError(500, ISE_SUBERR_LONGMETHODNAME);
        }

        // check for params
        char *szLeftPar = strchr(szName, '(');
        if(szLeftPar)
        {
            *szLeftPar = '\0';
            szLeftPar++;

            char *szRightPar = strchr(szLeftPar, ')');
            if(!szRightPar)
                return AtlsHttpError(500, ISE_SUBERR_UNEXPECTED);

            *szRightPar = '\0';

            szMethodName = szName;

        }

        // No handler name is specified, so we look up the method name in
        // T's replacement method map
        const CTagReplacerMethodEntry<T> *pEntry = NULL;
        pT->GetReplacementMethodMap(&pEntry);

        hcErr = FindReplacementOffsetInMap(szMethodName, pdwMethodOffset, pEntry);
        if(hcErr != HTTP_SUCCESS)
        {
            pT->GetAttrReplacementMethodMap(&pEntry);
            hcErr = FindReplacementOffsetInMap(szMethodName, pdwMethodOffset, pEntry);
            if(hcErr == HTTP_SUCCESS)
                *pdwMap = STENCIL_ATTR_MAP;
        }
        else
        {
            *pdwMap = STENCIL_BASIC_MAP;
        }

        // This assert will be triggered if arguments are passed to a replacement method that doesn't handle them
        ATLASSERT(szLeftPar == NULL || (szLeftPar != NULL && (pEntry != NULL && pEntry[*pdwMethodOffset].Methods.pfnParse != NULL)));

        if(hcErr == HTTP_SUCCESS && pEntry && pEntry[*pdwMethodOffset].Methods.pfnParse)
            hcErr = (pT->*pEntry[*pdwMethodOffset].Methods.pfnParse)(pMemMgr, szLeftPar, ppvParam);
        return hcErr;
    }

    HTTP_CODE FindReplacementOffsetInMap(
        LPCSTR szMethodName,
        LPDWORD pdwMethodOffset,
        const CTagReplacerMethodEntry<T> *pEntry) throw()
    {
        if(pEntry == NULL)
            return HTTP_FAIL;

        const CTagReplacerMethodEntry<T> *pEntryHead = pEntry;

        while(pEntry->szMethodName)
        {
            if(strcmp(pEntry->szMethodName, szMethodName) == 0)
            {
                if(pEntry->Methods.pfnMethod)
                {
                    *pdwMethodOffset = (DWORD)(pEntry - pEntryHead);
                    return HTTP_SUCCESS;
                }
            }
            pEntry++;
        }

        return HTTP_FAIL;
    }


    // Used to render a single replacement tag into a stream.
    // Looks up a pointer to a member function in user code by offseting into the users
    // replacement map. Much faster than the other overload of this function since
    // no string compares are performed.
    HTTP_CODE RenderReplacement(DWORD dwFnOffset, DWORD dwObjOffset, DWORD dwMap, void *pvParam)
    {
        HTTP_CODE hcErr = HTTP_FAIL;
        T *pT = static_cast<T *>(this);

        // if we were not passed an object offset, then we assume
        // that the function at dwFnOffset is in T's replacement
        // map
        if(dwObjOffset == STENCIL_INVALIDOFFSET)
        {
            // call a function in T's replacement map
            ATLASSERT(dwFnOffset != STENCIL_INVALIDOFFSET);
            const CTagReplacerMethodEntry<T> *pEntry = NULL;
            if(dwMap == STENCIL_BASIC_MAP)
                pT->GetReplacementMethodMap(&pEntry);
            else
                pT->GetAttrReplacementMethodMap(&pEntry);
            if(pEntry)
            {
                if(pEntry[dwFnOffset].nType == REPLACEMENT_ENTRY_DEFAULT)
                {
                    REPLACEMENT_METHOD pfn = NULL;
                    pfn = pEntry[dwFnOffset].Methods.pfnMethod;
                    ATLASSERT(pfn);
                    if(pfn)
                    {
                        hcErr = (pT->*pfn)();
                    }
                }
                else if(pEntry[dwFnOffset].nType == REPLACEMENT_ENTRY_ARGS)
                {
                    REPLACEMENT_METHOD_EX pfn = NULL;
                    pfn = pEntry[dwFnOffset].Methods.pfnMethodEx;
                    ATLASSERT(pfn);
                    if(pfn)
                    {
                        hcErr = (pT->*pfn)(pvParam);
                    }
                }
                else
                {
                    // unknown entry type
                    ATLASSERT(FALSE);
                }
            }
        }
        else
        {
            // otherwise, we were passed an object offset. The object
            // offset is a dword ID that T can use to look up the
            // ITagReplacer* of a tag replacer that will render this
            // replacement.
            CComPtr<ITagReplacer> spAltReplacer = NULL;
            if(!pT->GetReplacementObject(dwObjOffset, &spAltReplacer))
            {
                spAltReplacer->SetStream(m_pStream);
                hcErr = spAltReplacer->RenderReplacement(dwFnOffset, STENCIL_INVALIDOFFSET, dwMap, pvParam);
            }
        }
        return hcErr;
    }

    // Default GetHandlerOffset, does nothing
    HTTP_CODE GetHandlerOffset(LPCSTR /*szHandlerName*/, DWORD* pdwOffset)
    {
        if(pdwOffset)
            *pdwOffset = 0;
        return HTTP_FAIL;
    }

    // Default GetReplacementObject, does nothing
    HTTP_CODE GetReplacementObject(DWORD /*dwObjOffset*/, ITagReplacer **ppReplacer)
    {
        if(ppReplacer)
            *ppReplacer = NULL;
        return HTTP_FAIL;
    }

    void GetReplacementMethodMap(const CTagReplacerMethodEntry<T> ** ppOut) const
    {
        static const CTagReplacerMethodEntry<T> methods[] = { { NULL, NULL } };
        *ppOut = methods;
    }

    void GetAttrReplacementMethodMap(const CTagReplacerMethodEntry<T> **ppOut) const
    {
        static const CTagReplacerMethodEntry<T> methods[] = { { NULL, NULL } };
        *ppOut = methods;
    }

    HRESULT GetContext(REFIID, void**)
    {
        return E_NOINTERFACE;
    }

    virtual HINSTANCE GetResourceInstance()
    {
        return GetModuleHandle(NULL);
    }

    HTTP_CODE DefaultParseString(IAtlMemMgr *pMemMgr, LPCSTR szParams, char **ppParam) throw(...)
    {
        ATLENSURE(pMemMgr != NULL);
        ATLENSURE(szParams != NULL);
        ATLENSURE(ppParam != NULL);

        size_t nLen = strlen(szParams);
        if(nLen)
        {
            nLen++;
            *ppParam = (char *) pMemMgr->Allocate(nLen);
            if(*ppParam)
                Checked::memcpy_s(*ppParam, nLen, szParams, nLen);
            else
                return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM);
        }

        return HTTP_SUCCESS;
    }

    HTTP_CODE DefaultParseUChar(IAtlMemMgr *pMemMgr, LPCSTR szParams, unsigned char **ppParam) throw(...)
    {
        ATLENSURE(pMemMgr != NULL);
        ATLENSURE(szParams != NULL);
        ATLENSURE(ppParam != NULL);

        *ppParam = (unsigned char *) pMemMgr->Allocate(sizeof(unsigned char));
        if(*ppParam)
        {
            char *szEnd;
            **ppParam = (unsigned char) strtoul(szParams, &szEnd, 10);
        }
        else
        {
            return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM);
        }
        return HTTP_SUCCESS;
    }

    HTTP_CODE DefaultParseShort(IAtlMemMgr *pMemMgr, LPCSTR szParams, short **ppParam) throw(...)
    {
        ATLENSURE(pMemMgr != NULL);
        ATLENSURE(szParams != NULL);
        ATLENSURE(ppParam != NULL);

        *ppParam = (short *) pMemMgr->Allocate(sizeof(short));
        if(*ppParam)
        {
            **ppParam = (short)atoi(szParams);
        }
        else
        {
            return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM);
        }
        return HTTP_SUCCESS;
    }

    HTTP_CODE DefaultParseUShort(IAtlMemMgr *pMemMgr, LPCSTR szParams, unsigned short **ppParam) throw(...)
    {
        ATLENSURE(pMemMgr != NULL);
        ATLENSURE(szParams != NULL);
        ATLENSURE(ppParam != NULL);

        *ppParam = (unsigned short *) pMemMgr->Allocate(sizeof(short));
        if(*ppParam)
        {
            char *szEnd;
            **ppParam = (unsigned short) strtoul(szParams, &szEnd, 10);
        }
        else
        {
            return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM);
        }
        return HTTP_SUCCESS;
    }

    HTTP_CODE DefaultParseInt(IAtlMemMgr *pMemMgr, LPCSTR szParams, int **ppParam) throw(...)
    {
        ATLENSURE(pMemMgr != NULL);
        ATLENSURE(szParams != NULL);
        ATLENSURE(ppParam != NULL);

        *ppParam = (int *) pMemMgr->Allocate(sizeof(int));
        if(*ppParam)
        {
            **ppParam = atoi(szParams);
        }
        else
        {
            return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM);
        }
        return HTTP_SUCCESS;
    }

    HTTP_CODE DefaultParseUInt(IAtlMemMgr *pMemMgr, LPCSTR szParams, unsigned int **ppParam) throw(...)
    {
        ATLENSURE(pMemMgr != NULL);
        ATLENSURE(szParams != NULL);
        ATLENSURE(ppParam != NULL);

        *ppParam = (unsigned int *) pMemMgr->Allocate(sizeof(unsigned int));
        if(*ppParam)
        {
            char *szEnd;
            **ppParam = strtoul(szParams, &szEnd, 10);
        }
        else
        {
            return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM);
        }
        return HTTP_SUCCESS;
    }

    HTTP_CODE DefaultParseInt64(IAtlMemMgr *pMemMgr, LPCSTR szParams, __int64 **ppParam) throw(...)
    {
        ATLENSURE(pMemMgr != NULL);
        ATLENSURE(szParams != NULL);
        ATLENSURE(ppParam != NULL);

        *ppParam = (__int64 *) pMemMgr->Allocate(sizeof(__int64));
        if(*ppParam)
        {
            **ppParam = _atoi64(szParams);
        }
        else
        {
            return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM);
        }
        return HTTP_SUCCESS;
    }

    HTTP_CODE DefaultParseUInt64(IAtlMemMgr *pMemMgr, LPCSTR szParams, unsigned __int64 **ppParam) throw(...)
    {
        ATLENSURE(pMemMgr != NULL);
        ATLENSURE(szParams != NULL);
        ATLENSURE(ppParam != NULL);

        *ppParam = (unsigned __int64 *) pMemMgr->Allocate(sizeof(unsigned __int64));
        if(*ppParam)
        {
            char *szEnd;
            **ppParam = _strtoui64(szParams, &szEnd, 10);
        }
        else
        {
            return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM);
        }
        return HTTP_SUCCESS;
    }

    HTTP_CODE DefaultParseBool(IAtlMemMgr *pMemMgr, LPCSTR szParams, bool **ppParam) throw(...)
    {
        ATLENSURE(pMemMgr != NULL);
        ATLENSURE(szParams != NULL);
        ATLENSURE(ppParam != NULL);

        *ppParam = (bool *) pMemMgr->Allocate(sizeof(bool));
        if(*ppParam)
        {
            if(!_strnicmp(szParams, "true", sizeof("true") - sizeof('\0')))
                **ppParam = true;
            else
                **ppParam = false;
        }
        else
        {
            return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM);
        }

        return HTTP_SUCCESS;
    }

    HTTP_CODE DefaultParseDouble(IAtlMemMgr *pMemMgr, LPCSTR szParams, double **ppParam) throw(...)
    {
        ATLENSURE(pMemMgr != NULL);
        ATLENSURE(szParams != NULL);
        ATLENSURE(ppParam != NULL);

        *ppParam = (double *) pMemMgr->Allocate(sizeof(double));
        if(*ppParam)
        {
            **ppParam = atof(szParams);
        }
        else
        {
            return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM);
        }
        return HTTP_SUCCESS;
    }

    HTTP_CODE DefaultParseFloat(IAtlMemMgr *pMemMgr, LPCSTR szParams, float **ppParam) throw(...)
    {
        ATLENSURE(pMemMgr != NULL);
        ATLENSURE(szParams != NULL);
        ATLENSURE(ppParam != NULL);

        errno_t errnoValue = 0;
        *ppParam = (float *) pMemMgr->Allocate(sizeof(float));
        if(*ppParam)
        {
            errno_t saveErrno = Checked::get_errno();
            Checked::set_errno(0);
            **ppParam = (float) atof(szParams);
            errnoValue = Checked::get_errno();
            Checked::set_errno(saveErrno);
        }
        else
        {
            return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM);
        }
        if((**ppParam == -HUGE_VAL) || (**ppParam == HUGE_VAL) || (errnoValue == ERANGE))
        {
            return HTTP_FAIL;
        }
        return HTTP_SUCCESS;
    }
};

inline LPCSTR SkipSpace(LPCSTR sz, WORD nCodePage) throw()
{
    if(sz == NULL)
        return NULL;

    while(isspace(static_cast<unsigned char>(*sz)))
        sz = CharNextExA(nCodePage, sz, 0);
    return sz;
}

inline LPCSTR RSkipSpace(LPCSTR pStart, LPCSTR sz, WORD nCodePage) throw()
{
    if(sz == NULL || pStart == NULL)
        return NULL;

    while(isspace(static_cast<unsigned char>(*sz)) && sz != pStart)
        sz = CharPrevExA(nCodePage, pStart, sz, 0);
    return sz;
}

//
// StencilToken
// The stencil class will create an array of these tokens during the parse
// phase and use them during rendering to render the stencil
struct StencilToken
{
    LPCSTR pStart; // Start of fragment to be rendered
    LPCSTR pEnd; // End of fragment to be rendered
    DWORD type; // Type of token
    DWORD dwFnOffset; // Offset into the replacement map for the handler function.
    DWORD dwMap;
    DWORD dwObjOffset; // An identifier for the caller to use in identifiying the
    // object that will render this token.
    CHAR szHandlerName[ATL_MAX_HANDLER_NAME_LEN + 1]; // Name of handler object.
    CHAR szMethodName[ATL_MAX_METHOD_NAME_LEN + 1]; // Name of handler method.
    DWORD dwLoopIndex; // Offset into array of StencilTokens of the other loop tag
    DWORD_PTR dwData;
    BOOL bDynamicAlloc;
};


//
// Class CStencil
// The CStencil class is used to map in a stencil from a file or resource
// and parse the stencil into an array of StencilTokens. We then render
// the stencil from the array of tokens. This class's parse and render
// functions depend on an IReplacementHandlerLookup interface pointer to be
// passed so it can retrieve the IReplacementHandler interface pointer of the
// handler object that will be called to render replacement tags
class CStencil :
    public IMemoryCacheClient
{
private:
    LPCSTR m_pBufferStart; // Beginning of CHAR buffer that holds the stencil.
    // For mapped files this is the beginning of the mapping.
    LPCSTR m_pBufferEnd; // End of CHAR buffer that holds the stencil.
    CAtlArray<StencilToken> m_arrTokens; //An array of tokens.
    FILETIME m_ftLastModified;  // Last modified time (0 for resource)
    FILETIME m_ftLastChecked;   // Last time we retrieved last modified time (0 for resource)
    HCACHEITEM m_hCacheItem;
    WORD m_nCodePage;
    BOOL m_bUseLocaleACP;
    char m_szDllPath[MAX_PATH];
    char m_szHandlerName[ATL_MAX_HANDLER_NAME_LEN+1];  // Room for the path, the handler
    // the '/' and the '\0'
#ifdef ATL_DEBUG_STENCILS
    struct ParseError
    {
        char m_szError[ATL_STENCIL_MAX_ERROR_LEN];
        LPCSTR m_szPosition;
        LPCSTR m_szStartLine;
        LPCSTR m_szEndLine;
        int m_nLineNumber;

        bool operator==(const ParseError& that) const throw()
        {
            return (m_nLineNumber == that.m_nLineNumber);
        }
    };

    CSimpleArray<ParseError> m_Errors;
    HINSTANCE m_hResInst;

    class CParseErrorProvider : public ITagReplacerImpl<CParseErrorProvider>,
        public CComObjectRootEx<CComSingleThreadModel>
    {
    public:
        BEGIN_COM_MAP(CParseErrorProvider)
        COM_INTERFACE_ENTRY(ITagReplacer)
        END_COM_MAP()
        CSimpleArray<ParseError> *m_pErrors;
        int m_nCurrentError;

        CParseErrorProvider() throw() :
            m_pErrors(NULL),
            m_nCurrentError(-1)
        {
        }

        void Initialize(CSimpleArray<ParseError> *pErrors) throw()
        {
            m_pErrors = pErrors;
        }

        HTTP_CODE OnGetNextError() throw()
        {
            m_nCurrentError++;
            if(m_nCurrentError >= m_pErrors->GetSize() ||
            m_nCurrentError < 0)
            {
                m_nCurrentError = -1;
                return HTTP_S_FALSE;
            }
            else
                return HTTP_SUCCESS;
        }

        HTTP_CODE OnGetErrorLineNumber() throw(...)
        {
            if(m_pErrors->GetSize() == 0)
                return HTTP_SUCCESS;
            if(m_nCurrentError > m_pErrors->GetSize() ||
            m_nCurrentError < 0)
                m_nCurrentError = 0;

            CWriteStreamHelper c(m_pStream);
            if(!c.Write((*m_pErrors)[m_nCurrentError].m_nLineNumber))
                return HTTP_FAIL;

            return HTTP_SUCCESS;
        }

        HTTP_CODE OnGetErrorText() throw(...)
        {
            if(m_pErrors->GetSize() == 0)
                return HTTP_SUCCESS;
            if(m_nCurrentError > m_pErrors->GetSize() ||
            m_nCurrentError < 0)
                m_nCurrentError = 0;

            CWriteStreamHelper c(m_pStream);

            if(!c.Write(static_cast<LPCSTR>((*m_pErrors)[m_nCurrentError].m_szError)))
                return HTTP_FAIL;

            return HTTP_SUCCESS;
        }

        HTTP_CODE OnGetErrorLine() throw(...)
        {
            ATLASSUME(m_pStream != NULL);

            if(m_pErrors->GetSize() == 0)
                return HTTP_SUCCESS;
            if(m_nCurrentError > m_pErrors->GetSize() ||
            m_nCurrentError < 0)
                m_nCurrentError = 0;

            m_pStream->WriteStream((*m_pErrors)[m_nCurrentError].m_szStartLine,
            (int)((*m_pErrors)[m_nCurrentError].m_szEndLine - (*m_pErrors)[m_nCurrentError].m_szStartLine), NULL);

            return HTTP_SUCCESS;
        }

        BEGIN_REPLACEMENT_METHOD_MAP(CParseErrorProvider)
        REPLACEMENT_METHOD_ENTRY("GetNextError", OnGetNextError)
        REPLACEMENT_METHOD_ENTRY("GetErrorText", OnGetErrorText)
        REPLACEMENT_METHOD_ENTRY("GetErrorLine", OnGetErrorLine)
        REPLACEMENT_METHOD_ENTRY("GetErrorLineNumber", OnGetErrorLineNumber)
        END_REPLACEMENT_METHOD_MAP()
    };

#else
    bool m_bErrorsOccurred;
#endif

    class CSaveThreadLocale
    {
        LCID m_locale;
    public:
        CSaveThreadLocale() throw()
        {
            m_locale = GetThreadLocale();
        }
        ~CSaveThreadLocale() throw()
        {
            SetThreadLocale(m_locale);
        }
    };

    HTTP_CODE LoadFromResourceInternal(HINSTANCE hInstRes, HRSRC hRsrc) throw()
    {
        ATLASSERT(hRsrc != NULL);

        HGLOBAL hgResource = NULL;
        hgResource = LoadResource(hInstRes, hRsrc);
        if(!hgResource)
        {
            return HTTP_FAIL;
        }

        DWORD dwSize = SizeofResource(hInstRes, hRsrc);
        if(dwSize != 0)
        {
            m_pBufferStart = (LPSTR)LockResource(hgResource);
            if(m_pBufferStart != NULL)
            {
                m_pBufferEnd = m_pBufferStart + dwSize;
                return HTTP_SUCCESS;
            }
        }

        // failed to load resource
        return HTTP_FAIL;
    }

protected:

    ITagReplacer *m_pReplacer;
    IAtlMemMgr *m_pMemMgr;
    static CCRTHeap m_crtHeap;

    inline BOOL CheckTag(LPCSTR szTag, DWORD dwTagLen, LPCSTR szStart, DWORD dwLen) throw()
    {
        if(dwLen < dwTagLen)
            return FALSE;

        if(memcmp(szStart, szTag, dwTagLen))
            return FALSE;

        if(isspace(static_cast<unsigned char>(szStart[dwTagLen])) || szStart[dwTagLen] == '}')
            return TRUE;

        return FALSE;
    }

    inline void FindTagArgs(LPCSTR& szstart, LPCSTR& szend, int nKeywordChars) throw()
    {
        // this function should only be called after finding a valid tag
        // the first two characters of szstart should be {{
        ATLASSERT(szstart[0] == '{' && szstart[1] == '{');

        if(*szstart == '{')
            szstart += 2; // move past {{
        szstart = SkipSpace(szstart, m_nCodePage); // move past whitespace
        szstart += nKeywordChars; // move past keyword
        szstart = SkipSpace(szstart, m_nCodePage); // move past whitespace after keyword
        if(*szend == '}')
            szend -= 2; // chop off }}
        szend = RSkipSpace(szstart, szend, m_nCodePage); // chop of trailing whitespace
    }

    DWORD CheckTopAndPop(DWORD *pBlockStack, DWORD *pdwTop, DWORD dwToken) throw()
    {
        if(*pdwTop == 0)
            return STENCIL_INVALIDINDEX;
        if(m_arrTokens[pBlockStack[*pdwTop]].type == dwToken)
        {
            *pdwTop = (*pdwTop) - 1;
            return pBlockStack[(*pdwTop)+1];
        }
        return STENCIL_INVALIDINDEX;
    }

    DWORD PushToken(DWORD *pBlockStack, DWORD *pdwTop, DWORD dwIndex) throw()
    {
        if(*pdwTop < (ATL_MAX_BLOCK_STACK - 1))
        {
            *pdwTop = (*pdwTop) + 1;
            pBlockStack[*pdwTop] = dwIndex;
        }
        else
        {
            dwIndex = STENCIL_INVALIDINDEX;
        }

        return dwIndex;
    }

public:

    enum PARSE_TOKEN_RESULT { INVALID_TOKEN, NORMAL_TOKEN, RESERVED_TOKEN };

    CStencil(IAtlMemMgr *pMemMgr = NULL) throw()
    {
        m_pBufferStart = NULL;
        m_pBufferEnd = NULL;
        m_hCacheItem = NULL;
        m_ftLastModified.dwLowDateTime = 0;
        m_ftLastModified.dwHighDateTime = 0;
        m_ftLastChecked.dwLowDateTime = 0;
        m_ftLastChecked.dwHighDateTime = 0;
        m_arrTokens.SetCount(0, 128);
        m_nCodePage = CP_ACP;
        m_bUseLocaleACP = TRUE;
        m_szHandlerName[0] = '\0';
        m_szDllPath[0] = '\0';
        m_pMemMgr = pMemMgr;
        if(!pMemMgr)
            m_pMemMgr = &m_crtHeap;
#ifdef ATL_DEBUG_STENCILS
        m_hResInst = NULL;
#else
        m_bErrorsOccurred = false;
#endif

    }

    virtual ~CStencil() throw()
    {
        Uninitialize();
    }

#ifdef ATL_DEBUG_STENCILS

    bool RenderErrors(IWriteStream *pStream) throw(...)
    {
        if(pStream == NULL)
        {
            return false;
        }

        CComObjectStackEx<CParseErrorProvider> Errors;

        Errors.Initialize(&m_Errors);
        CStencil ErrorStencil;

        if(m_hResInst != NULL)
        {
            CFixedStringT<CStringA, 256> strErrorStencil;
            _ATLTRY
            {
                if(strErrorStencil.LoadString(m_hResInst, IDS_STENCIL_ERROR_STENCIL) == FALSE)
                {
                    return false;
                }
            }
            _ATLCATCHALL()
            {
                return false;
            }

            HTTP_CODE hcRet = ErrorStencil.LoadFromString(strErrorStencil, strErrorStencil.GetLength());

            if(hcRet == HTTP_SUCCESS)
            {
                if(ErrorStencil.ParseReplacements(static_cast<ITagReplacer *>(&Errors)) != false)
                {
                    ErrorStencil.FinishParseReplacements();
                    if(ErrorStencil.ParseSuccessful() != false)
                    {
                        hcRet = ErrorStencil.Render(static_cast<ITagReplacer *>(&Errors), pStream);

                        if(HTTP_ERROR_CODE(hcRet) < 400)
                        {
                            return true;
                        }
                    }
                }
            }
        }

        return false;
    }

    void SetErrorResource(HINSTANCE hResInst)
    {
        m_hResInst = hResInst;
    }

    bool ParseSuccessful()
    {
        return (m_Errors.GetSize() == 0);
    }

    bool AddErrorInternal(LPCSTR szErrorText, LPCSTR szPosition) throw()
    {
        int nLineNum = 0;
        LPCSTR szStartLine = NULL;
        LPCSTR szPtr = m_pBufferStart;
        while(szPtr < szPosition)
        {
            if(*szPtr == '\n')
            {
                szStartLine = szPtr + 1;
                nLineNum++;
            }

            LPSTR szNext = CharNextExA(m_nCodePage, szPtr, 0);
            if(szNext == szPtr)
            {
                break;
            }
            szPtr = szNext;
        }
        LPCSTR szEndLine = szPtr;
        while(*szPtr)
        {
            if(*szPtr == '\n')
                break;
            szEndLine = szPtr;
            LPSTR szNext = CharNextExA(m_nCodePage, szPtr, 0);
            if(szNext == szPtr)
            {
                break;
            }
            szPtr = szNext;
        }

        ParseError p;
        SafeStringCopy(p.m_szError, szErrorText);
        p.m_szPosition = szPosition;
        p.m_nLineNumber = nLineNum;
        p.m_szStartLine = szStartLine;
        p.m_szEndLine = szEndLine;

        return (m_Errors.Add(p) == TRUE);
    }

    bool AddError(UINT uID, LPCSTR szPosition) throw()
    {
        if(m_hResInst != NULL)
        {
            _ATLTRY
            {
                CFixedStringT<CStringA, 256> strRes;
                if(strRes.LoadString(m_hResInst, uID) != FALSE)
                {
                    return AddErrorInternal(strRes, szPosition);
                }
            }
            _ATLCATCHALL()
            {
            }
        }
        return AddErrorInternal("Could not load resource for error string", szPosition);
    }

    bool AddReplacementError(LPCSTR szReplacement, LPCSTR szPosition) throw()
    {
        if(m_hResInst != NULL)
        {
            _ATLTRY
            {
                CFixedStringT<CStringA, 256> strRes;
                if(strRes.LoadString(m_hResInst, IDS_STENCIL_UNRESOLVED_REPLACEMENT) != FALSE)
                {
                    CFixedStringT<CStringA, 256> strErrorText;
                    strErrorText.Format(strRes, szReplacement);
                    return AddErrorInternal(strErrorText, szPosition);
                }
                else
                {
                    return AddErrorInternal("Could not load resource for error string", szPosition);
                }
            }
            _ATLCATCHALL()
            {
                return false;
            }
        }

        return false;
    }

#else

    bool ParseSuccessful()
    {
        return !m_bErrorsOccurred;
    }

    bool AddError(UINT /*uID*/, LPCSTR /*szPosition*/) throw()
    {
        m_bErrorsOccurred = true;
        return true;
    }

    bool AddReplacementError(LPCSTR /*szReplacement*/, LPCSTR /*szPosition*/) throw()
    {
        m_bErrorsOccurred = true;
        return true;
    }

    void SetErrorResource(HINSTANCE) throw()
    {
    }

#endif

    // Call Uninitialize if you want to re-use an already initialized CStencil
    void Uninitialize() throw()
    {
        int nSize = (int) m_arrTokens.GetCount();
        for(int nIndex = 0; nIndex < nSize; nIndex++)
        {
            if(m_arrTokens[nIndex].bDynamicAlloc)
                delete [] m_arrTokens[nIndex].pStart;
            if(m_arrTokens[nIndex].dwData != 0 && m_arrTokens[nIndex].type != STENCIL_LOCALE)
                m_pMemMgr->Free((void *) m_arrTokens[nIndex].dwData);
        }

        m_arrTokens.RemoveAll();
        if((m_ftLastModified.dwLowDateTime || m_ftLastModified.dwHighDateTime) && m_pBufferStart)
        {
            delete [] m_pBufferStart;
        }
        m_pBufferStart = NULL;
        m_pBufferEnd = NULL;
    }

    void GetLastModified(FILETIME *pftLastModified)
    {
        ATLENSURE(pftLastModified);
        *pftLastModified = m_ftLastModified;
    }

    void GetLastChecked(FILETIME *pftLastChecked)
    {
        ATLENSURE(pftLastChecked);
        *pftLastChecked = m_ftLastChecked;
    }

    void SetLastChecked(FILETIME *pftLastChecked)
    {
        ATLENSURE(pftLastChecked);
        m_ftLastChecked = *pftLastChecked;
    }

    HCACHEITEM GetCacheItem()
    {
        return m_hCacheItem;
    }

    void SetCacheItem(HCACHEITEM hCacheItem)
    {
        ATLASSUME(m_hCacheItem == NULL);
        m_hCacheItem = hCacheItem;
    }

    bool GetHandlerName(__out_ecount_z(nPathLen) LPSTR szDllPath, __in size_t nPathLen, __out_ecount_z(nHandlerNameLen) LPSTR szHandlerName, __in size_t nHandlerNameLen) throw()
    {
        if(strlen(m_szDllPath) >= nPathLen)
        {
            return false;
        }

        if(strlen(m_szHandlerName) >= nHandlerNameLen)
        {
            return false;
        }

        if(0 != strcpy_s(szDllPath, nPathLen, m_szDllPath))
        {
            return false;
        }

        if(0 != strcpy_s(szHandlerName, nHandlerNameLen, m_szHandlerName))
        {
            return false;
        }

        return true;
    }

    // Adds a token to the token array, handler name, method name
    // and handler function offset are optional
    ATL_NOINLINE DWORD AddToken(
        LPCSTR pStart,
        LPCSTR pEnd,
        DWORD dwType,
        LPCSTR szHandlerName = NULL,
        LPCSTR szMethodName = NULL,
        DWORD dwFnOffset = STENCIL_INVALIDOFFSET,
        DWORD dwObjOffset = STENCIL_INVALIDOFFSET,
        DWORD_PTR dwData = 0,
        DWORD dwMap = 0,
        BOOL bDynamicAlloc = 0) throw()
    {
        StencilToken t;

        memset(&t, 0x00, sizeof(t));

        t.pStart = pStart;
        t.pEnd = pEnd;
        t.type = dwType;
        t.dwLoopIndex = STENCIL_INVALIDINDEX;
        t.dwFnOffset = dwFnOffset;
        t.dwObjOffset = dwObjOffset;
        t.dwData = dwData;
        t.dwMap = dwMap;
        t.bDynamicAlloc = bDynamicAlloc;

        // this should never assert unless the user has overriden something incorrectly
        if((szHandlerName != NULL) && (*szHandlerName))
        {
            ATLVERIFY(SafeStringCopy(t.szHandlerName, szHandlerName));
        }
        if((szMethodName != NULL) && (*szMethodName))
        {
            ATLVERIFY(SafeStringCopy(t.szMethodName, szMethodName));
        }

        _ATLTRY
        {
            return (DWORD) m_arrTokens.Add(t);
        }
        _ATLCATCHALL()
        {
            return STENCIL_INVALIDINDEX;
        }
    }

    HTTP_CODE LoadFromFile(LPCSTR szFileName) throw()
    {
        HRESULT hr = E_FAIL;
        ULONGLONG dwLen = 0;
        CAtlFile file;

        _ATLTRY
        {
            hr = file.Create(CA2CTEX<MAX_PATH>(szFileName), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING);
            if(FAILED(hr) || GetFileType(file) != FILE_TYPE_DISK)
                return AtlsHttpError(500, ISE_SUBERR_STENCIL_LOAD_FAIL); // couldn't load SRF!

            if(GetFileTime(file, NULL, NULL, &m_ftLastModified))
            {
                if(SUCCEEDED(file.GetSize(dwLen)))
                {
                    ATLASSERT(!m_pBufferStart);

                    GetSystemTimeAsFileTime(&m_ftLastChecked);

                    m_pBufferStart = NULL;
                    CAutoVectorPtr<char> buffer;
                    if(!buffer.Allocate((size_t) dwLen))
                        return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM); // out of memory

                    DWORD dwRead = 0;
                    hr = file.Read(buffer, (DWORD) dwLen, dwRead);
                    if(FAILED(hr))
                        return AtlsHttpError(500, ISE_SUBERR_READFILEFAIL); // ReadFile failed

                    m_pBufferStart = buffer.Detach();
                    m_pBufferEnd = m_pBufferStart + dwRead;
                }
            }
        }
        _ATLCATCHALL()
        {
            return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM);
        }

        return HTTP_SUCCESS;
    }

    // loads a stencil from the specified resource.
    HTTP_CODE LoadFromResource(HINSTANCE hInstRes, LPCSTR szID, LPCSTR szType = NULL) throw()
    {
        if(szType == NULL)
        {
            szType = (LPCSTR) RT_HTML;
        }

        HRSRC hRsrc = FindResourceA(hInstRes, szID, szType);
        if(hRsrc != NULL)
        {
            return LoadFromResourceInternal(hInstRes, hRsrc);
        }

        return HTTP_FAIL;
    }

    HTTP_CODE LoadFromResourceEx(HINSTANCE hInstRes, LPCSTR szID, WORD wLanguage, LPCSTR szType = NULL) throw()
    {
        if(szType == NULL)
        {
            szType = (LPCSTR) RT_HTML;
        }

        HRSRC hRsrc = FindResourceExA(hInstRes, szType, szID, wLanguage);
        if(hRsrc != NULL)
        {
            return LoadFromResourceInternal(hInstRes, hRsrc);
        }

        return HTTP_FAIL;
    }

    // loads a stencil from the specified resource
    HTTP_CODE LoadFromResource(HINSTANCE hInstRes, UINT nId, LPCSTR szType = NULL) throw()
    {
        return LoadFromResource(hInstRes, MAKEINTRESOURCEA(nId), szType);
    }

    HTTP_CODE LoadFromResourceEx(HINSTANCE hInstRes, UINT nId, WORD wLanguage, LPCSTR szType = NULL) throw()
    {
        return LoadFromResourceEx(hInstRes, MAKEINTRESOURCEA(nId), wLanguage, szType);
    }

    // loads a stencil from a string
    HTTP_CODE LoadFromString(LPCSTR szString, DWORD dwSize) throw()
    {
        m_pBufferStart = szString;
        m_pBufferEnd = m_pBufferStart + dwSize;
        return HTTP_SUCCESS;
    }

    // Cracks the loaded stencil into an array of StencilTokens in preparation for
    // rendering. LoadStencil must be called prior to calling this function.
    virtual bool ParseReplacements(ITagReplacer* pReplacer) throw(...)
    {
        return ParseReplacementsFromBuffer(pReplacer, GetBufferStart(), GetBufferEnd());
    }

    virtual bool FinishParseReplacements() throw(...)
    {
        DWORD dwSize = (DWORD) m_arrTokens.GetCount();
        for(DWORD dwIndex = 0; dwIndex < dwSize; dwIndex++)
        {
            StencilToken& token = m_arrTokens[dwIndex];
            bool bUnclosedBlock = ((token.type == STENCIL_CONDITIONALSTART ||
                                    token.type == STENCIL_CONDITIONALELSE ||
                                    token.type == STENCIL_ITERATORSTART) &&
                                   token.dwLoopIndex == STENCIL_INVALIDINDEX);
            if((token.szMethodName[0] && token.dwFnOffset == STENCIL_INVALIDOFFSET) || bUnclosedBlock)
            {
                if(bUnclosedBlock ||
                   m_pReplacer->FindReplacementOffset(
                       token.szMethodName, &token.dwFnOffset,
                       token.szHandlerName, &token.dwObjOffset,
                       &token.dwMap, (void **)(&token.dwData), m_pMemMgr) != HTTP_SUCCESS)
                {
                    if(bUnclosedBlock && token.type == STENCIL_CONDITIONALSTART)
                    {
                        AddError(IDS_STENCIL_UNCLOSEDBLOCK_IF, token.pStart);
                    }
                    else if(bUnclosedBlock && token.type == STENCIL_CONDITIONALELSE)
                    {
                        AddError(IDS_STENCIL_UNCLOSEDBLOCK_ELSE, token.pStart);
                    }
                    else if(bUnclosedBlock && token.type == STENCIL_ITERATORSTART)
                    {
                        AddError(IDS_STENCIL_UNCLOSEDBLOCK_WHILE, token.pStart);
                    }
                    else
                    {
                        AddReplacementError(token.szMethodName, token.pStart);
                    }

                    // unresolved replacement, convert it to a text token
                    token.type = STENCIL_TEXTTAG;

                    // convert all linked tokens to text tokens as well
                    // this includes: endif, else, endwhile
                    DWORD dwLoopIndex = token.dwLoopIndex;
                    while(dwLoopIndex != dwIndex && dwLoopIndex != STENCIL_INVALIDINDEX)
                    {
                        m_arrTokens[dwLoopIndex].type = STENCIL_TEXTTAG;
                        dwLoopIndex = m_arrTokens[dwLoopIndex].dwLoopIndex;
                    }
                }
            }
        }

        return ParseSuccessful();
    }

    virtual bool Parse(ITagReplacer *pReplacer) throw(...)
    {
        if(ParseReplacements(pReplacer))
        {
            return FinishParseReplacements();
        }
        return false;
    }


    DWORD ParseReplacement(LPCSTR szTokenStart,
                           LPCSTR szTokenEnd,
                           DWORD dwTokenType = STENCIL_REPLACEMENT,
                           DWORD dwKeywordLen = 0) throw()
    {
        // hold on to the start and end pointers (before removing curlies and whitespace)
        // this is needed so that we can convert the token to a text token if the method
        // is not resolved (in FinishParseReplacements)
        LPCSTR szStart = szTokenStart;
        LPCSTR szEnd = szTokenEnd;

        FindTagArgs(szTokenStart, szTokenEnd, dwKeywordLen);

        char szMethodName[ATL_MAX_METHOD_NAME_LEN+1];
        char szHandlerName[ATL_MAX_HANDLER_NAME_LEN+1];

        DWORD dwIndex;
        //look up the handler name, method name and handler interface
        if(HTTP_SUCCESS == GetHandlerAndMethodNames(szTokenStart, szTokenEnd,
                szMethodName, ATL_MAX_METHOD_NAME_LEN + 1,
                szHandlerName, ATL_MAX_HANDLER_NAME_LEN + 1))
            dwIndex = AddToken(szStart, szEnd, dwTokenType,
                               szHandlerName, szMethodName,
                               STENCIL_INVALIDINDEX, STENCIL_INVALIDINDEX,
                               0, STENCIL_BASIC_MAP);
        else
            dwIndex = STENCIL_INVALIDINDEX;
        return dwIndex;
    }

    DWORD ParseWhile(LPCSTR szTokenStart, LPCSTR szTokenEnd, DWORD *pBlockStack, DWORD *pdwTop) throw()
    {
        DWORD dwIndex = ParseReplacement(szTokenStart, szTokenEnd, STENCIL_ITERATORSTART, sizeof("while") - 1);
        if(dwIndex == STENCIL_INVALIDINDEX)
            return dwIndex;
        return PushToken(pBlockStack, pdwTop, dwIndex);
    }

    DWORD ParseEndWhile(LPCSTR szTokenStart, LPCSTR szTokenEnd, DWORD *pBlockStack, DWORD *pdwTop) throw()
    {
        DWORD dwTopIndex = CheckTopAndPop(pBlockStack, pdwTop, STENCIL_ITERATORSTART);
        if(dwTopIndex == STENCIL_INVALIDINDEX)
        {
            AddError(IDS_STENCIL_UNOPENEDBLOCK_ENDWHILE, szTokenStart);
            return dwTopIndex;
        }

        DWORD dwIndex = AddToken(szTokenStart, szTokenEnd, STENCIL_ITERATOREND);
        if(dwIndex != STENCIL_INVALIDINDEX)
        {
            m_arrTokens[dwTopIndex].dwLoopIndex = dwIndex;
            m_arrTokens[dwIndex].dwLoopIndex = dwTopIndex;
        }
        return dwIndex;
    }

    DWORD ParseIf(LPCSTR szTokenStart, LPCSTR szTokenEnd, DWORD *pBlockStack, DWORD *pdwTop) throw()
    {
        DWORD dwIndex = ParseReplacement(szTokenStart, szTokenEnd, STENCIL_CONDITIONALSTART, sizeof("if") - 1);
        if(dwIndex == STENCIL_INVALIDINDEX)
            return dwIndex;
        return PushToken(pBlockStack, pdwTop, dwIndex);
    }

    DWORD ParseElse(LPCSTR szTokenStart, LPCSTR szTokenEnd, DWORD *pBlockStack, DWORD *pdwTop) throw()
    {
        DWORD dwTopIndex = CheckTopAndPop(pBlockStack, pdwTop, STENCIL_CONDITIONALSTART);
        if(dwTopIndex == STENCIL_INVALIDINDEX)
        {
            AddError(IDS_STENCIL_UNOPENEDBLOCK_ELSE, szTokenStart);
            return dwTopIndex;
        }

        DWORD dwIndex = AddToken(szTokenStart, szTokenEnd, STENCIL_CONDITIONALELSE);
        if(dwIndex != STENCIL_INVALIDINDEX)
        {
            m_arrTokens[dwTopIndex].dwLoopIndex = dwIndex;

            return PushToken(pBlockStack, pdwTop, dwIndex);
        }
        return dwIndex;
    }

    DWORD ParseEndIf(LPCSTR szTokenStart, LPCSTR szTokenEnd, DWORD *pBlockStack, DWORD *pdwTop) throw()
    {
        DWORD dwTopIndex = CheckTopAndPop(pBlockStack, pdwTop, STENCIL_CONDITIONALSTART);
        if(dwTopIndex == STENCIL_INVALIDINDEX)
        {
            dwTopIndex = CheckTopAndPop(pBlockStack, pdwTop, STENCIL_CONDITIONALELSE);
            if(dwTopIndex == STENCIL_INVALIDINDEX)
            {
                AddError(IDS_STENCIL_UNOPENEDBLOCK_ENDIF, szTokenStart);
                return dwTopIndex;
            }
        }
        DWORD dwIndex = AddToken(szTokenStart, szTokenEnd, STENCIL_CONDITIONALEND);
        if(dwIndex != STENCIL_INVALIDINDEX)
        {
            m_arrTokens[dwTopIndex].dwLoopIndex = dwIndex;
        }
        return dwIndex;
    }

    DWORD ParseLocale(LPCSTR szTokenStart, LPCSTR szTokenEnd) throw()
    {
        LPCSTR szstart = szTokenStart;
        LPCSTR szend = szTokenEnd;
        LCID locale = 0xFFFFFFFF;
        FindTagArgs(szstart, szend, 6);
#ifndef ATL_NO_MLANG
        if(isdigit(static_cast<unsigned char>(szstart[0])))
        {
            locale = (LCID) atoi(szstart);
        }
        else
        {
            HRESULT hr;

            CComPtr<IMultiLanguage> pML;
            hr = pML.CoCreateInstance(__uuidof(CMultiLanguage), NULL, CLSCTX_INPROC_SERVER);
            if(FAILED(hr))
            {
                ATLTRACE(atlTraceStencil, 0, _T("Couldn't create CMultiLanguage object. check MLANG installation."));
                AddError(IDS_STENCIL_MLANG_COCREATE, szTokenStart);
            }
            else
            {
                CStringW str(szstart, (int)((szend - szstart) + 1));

#ifdef __IMultiLanguage2_INTERFACE_DEFINED__

                // use IMultiLanguage2 if possible
                CComPtr<IMultiLanguage2> spML2;
                hr = pML.QueryInterface(&spML2);
                if(FAILED(hr) || !spML2.p)
                    hr = pML->GetLcidFromRfc1766(&locale, CComBSTR(str));
                else
                    hr = spML2->GetLcidFromRfc1766(&locale, CComBSTR(str));

#else // __IMultiLanguage2_INTERFACE_DEFINED__

                hr = pML->GetLcidFromRfc1766(&locale, CComBSTR(str));

#endif // __IMultiLanguage2_INTERFACE_DEFINED__

                if(FAILED(hr))
                {
                    AddError(IDS_STENCIL_MLANG_LCID, szTokenStart);
                }
            }
            if(FAILED(hr))
                locale = 0xFFFFFFFF;
        }
#else
        locale = (LCID) atoi(szstart);
#endif

        if(m_bUseLocaleACP)
        {
            TCHAR szACP[7];
            if(GetLocaleInfo(locale, LOCALE_IDEFAULTANSICODEPAGE, szACP, 7) != 0)
            {
                m_nCodePage = (WORD) _ttoi(szACP);
            }
            else
            {
                AddError(IDS_STENCIL_MLANG_GETLOCALE, szTokenStart);
            }
        }
        DWORD dwCurrentTokenIndex = STENCIL_INVALIDINDEX;
        if(locale != 0xFFFFFFFF)
            dwCurrentTokenIndex = AddToken(NULL, NULL, STENCIL_LOCALE,
                                           NULL, NULL, STENCIL_INVALIDOFFSET, STENCIL_INVALIDOFFSET, locale);
        else
            return STENCIL_INVALIDINDEX;
        return dwCurrentTokenIndex;
    }

    DWORD ParseCodepage(LPCSTR szTokenStart, LPCSTR szTokenEnd) throw()
    {
        LPCSTR szstart = szTokenStart;
        LPCSTR szend = szTokenEnd;
        WORD nCodePage = 0xFFFF;

        FindTagArgs(szstart, szend, 8);
#ifndef ATL_NO_MLANG
        if(isdigit(static_cast<unsigned char>(szstart[0])))
        {
            nCodePage = (WORD) atoi(szstart);
        }
        else
        {
            HRESULT hr;

            CComPtr<IMultiLanguage> pML;
            hr = pML.CoCreateInstance(__uuidof(CMultiLanguage), NULL, CLSCTX_INPROC_SERVER);
            if(FAILED(hr))
            {
                ATLTRACE(atlTraceStencil, 0, _T("Couldn't create CMultiLanguage object. check MLANG installation."));
                AddError(IDS_STENCIL_MLANG_COCREATE, szTokenStart);
            }
            else
            {
                CStringW str(szstart, (int)((szend - szstart) + 1));
                MIMECSETINFO info;

#ifdef __IMultiLanguage2_INTERFACE_DEFINED__

                // use IMultiLanguage2 if possible
                CComPtr<IMultiLanguage2> spML2;
                hr = pML.QueryInterface(&spML2);
                if(FAILED(hr) || !spML2.p)
                    hr = pML->GetCharsetInfo(CComBSTR(str), &info);
                else
                    hr = spML2->GetCharsetInfo(CComBSTR(str), &info);

#else // __IMultiLanguage2_INTERFACE_DEFINED__

                hr = pML->GetCharsetInfo(CComBSTR(str), &info);

#endif // __IMultiLanguage2_INTERFACE_DEFINED__

                // for most character sets, uiCodePage and uiInternetEncoding
                // are the same. UTF-8 is the exception that we're concerned about.
                // for that character set, we want uiInternetEncoding (65001 - UTF-8)
                // instead of uiCodePage (1200 - UCS-2)
                if(SUCCEEDED(hr))
                {
                    nCodePage = (WORD) info.uiInternetEncoding;
                }
                else
                {
                    AddError(IDS_STENCIL_MLANG_GETCHARSET, szTokenStart);
                }
            }
            if(FAILED(hr))
                nCodePage = 0xFFFF;
        }
#else
        nCodePage = (WORD) atoi(szstart);
#endif
        if(nCodePage != 0xFFFF)
            m_nCodePage = nCodePage;
        m_bUseLocaleACP = FALSE;

        return STENCIL_INVALIDINDEX;
    }

    PARSE_TOKEN_RESULT ParseHandler(LPCSTR szTokenStart, LPCSTR szTokenEnd) throw()
    {
        LPCSTR szstart = szTokenStart;
        LPCSTR szend = szTokenEnd;

        if(m_szHandlerName[0] && m_szDllPath[0])
            return RESERVED_TOKEN; // already found the handler and path dll names

        FindTagArgs(szstart, szend, 7);
        size_t nlen = (szend - szstart) + 1;
        char szHandlerDllName[MAX_PATH + ATL_MAX_HANDLER_NAME_LEN + 1];
        if(nlen < MAX_PATH + ATL_MAX_HANDLER_NAME_LEN + 1)
        {
            Checked::memcpy_s(szHandlerDllName, MAX_PATH + ATL_MAX_HANDLER_NAME_LEN + 1, szstart, szend - szstart + 1);
            szHandlerDllName[szend-szstart+1] = '\0';

            DWORD dwDllPathLen = MAX_PATH;
            DWORD dwHandlerNameLen = ATL_MAX_HANDLER_NAME_LEN + 1;

            if(!_AtlCrackHandler(szHandlerDllName, m_szDllPath, &dwDllPathLen, m_szHandlerName, &dwHandlerNameLen))
            {
                AddError(IDS_STENCIL_INVALID_HANDLER, szTokenStart);
                return INVALID_TOKEN;
            }
        }

        return RESERVED_TOKEN;
    }

    virtual PARSE_TOKEN_RESULT ParseToken(LPCSTR szTokenStart, LPCSTR szTokenEnd, DWORD *pBlockStack, DWORD *pdwTop)
    {
        LPCSTR pStart = szTokenStart;
        pStart += 2; //skip curlies
        pStart = SkipSpace(pStart, m_nCodePage);
        DWORD dwLen = (DWORD)(szTokenEnd - szTokenStart);

        DWORD dwIndex = STENCIL_INVALIDINDEX;
        PARSE_TOKEN_RESULT ret = RESERVED_TOKEN;

        if(CheckTag("endwhile", 8, pStart, dwLen))
            dwIndex = ParseEndWhile(szTokenStart, szTokenEnd, pBlockStack, pdwTop);
        else if(CheckTag("while", 5, pStart, dwLen))
            dwIndex = ParseWhile(szTokenStart, szTokenEnd, pBlockStack, pdwTop);
        else if(CheckTag("endif", 5, pStart, dwLen))
            dwIndex = ParseEndIf(szTokenStart, szTokenEnd, pBlockStack, pdwTop);
        else if(CheckTag("else", 4, pStart, dwLen))
            dwIndex = ParseElse(szTokenStart, szTokenEnd, pBlockStack, pdwTop);
        else if(CheckTag("if", 2, pStart, dwLen))
            dwIndex = ParseIf(szTokenStart, szTokenEnd, pBlockStack, pdwTop);
        else if(CheckTag("locale", 6, pStart, dwLen))
            dwIndex = ParseLocale(szTokenStart, szTokenEnd);
        else if(CheckTag("handler", 7, pStart, dwLen))
        {
            return ParseHandler(szTokenStart, szTokenEnd);
        }
        else if(CheckTag("codepage", 8, pStart, dwLen))
        {
            ParseCodepage(szTokenStart, szTokenEnd);
            return RESERVED_TOKEN;
        }
        else
        {
            dwIndex = ParseReplacement(szTokenStart, szTokenEnd, STENCIL_REPLACEMENT);
            if(dwIndex == STENCIL_INVALIDINDEX)
                return INVALID_TOKEN;
            ret = NORMAL_TOKEN;
        }

        if(dwIndex == STENCIL_INVALIDINDEX)
            return INVALID_TOKEN;
        return ret;
    }

    virtual bool ParseReplacementsFromBuffer(ITagReplacer* pReplacer, LPCSTR pStart, LPCSTR pEnd)
    {
        LPCSTR szCurr = pStart;
        DWORD BlockStack[ATL_MAX_BLOCK_STACK];
        DWORD dwTop = 0;

        m_pReplacer = pReplacer;

        DWORD dwCurrentTokenIndex = 0;

        if(!szCurr)
        {
            ATLASSERT(FALSE);
            AddError(IDS_STENCIL_NULLPARAM, NULL);
            return false;
        }

        LPCSTR szEnd = pEnd;
        if(szEnd <= szCurr)
        {
            ATLASSERT(FALSE);
            AddError(IDS_STENCIL_INVALIDSTRING, NULL);
            return true;
        }

        while(szCurr < szEnd)
        {
            //mark the start of this block, then find the end of the block
            //the end is denoted by an opening curly
            LPCSTR szStart = szCurr;
            while(szCurr < szEnd && (*szCurr != '{' || szCurr[1] != '{'))
            {
                LPSTR szNext = CharNextExA(m_nCodePage, szCurr, 0);
                if(szNext == szCurr)
                {
                    // embedded null
                    AddError(IDS_STENCIL_EMBEDDED_NULL, NULL);
                    return true;
                }
                szCurr = szNext;
            }

            //special case for the last text block, if there is one
            if(szCurr >= szEnd)
            {
                // add the last token. This is everything after the last
                // double curly block, which is text.
                dwCurrentTokenIndex = AddToken(szStart, szEnd - 1, STENCIL_TEXTTAG);
                break;
            }

            //if there are any characters between szStart and szCurr inclusive,
            //copy them to a text token.
            if(szCurr - 1 >= szStart)
                dwCurrentTokenIndex = AddToken(szStart, szCurr - 1, STENCIL_TEXTTAG);

            if(dwCurrentTokenIndex == STENCIL_INVALIDINDEX)
            {
                AddError(IDS_STENCIL_OUTOFMEMORY, pStart);
                return false;
            }

            //find the end of the tag
            LPSTR szEndTag;
            szStart = szCurr;
            szCurr += 2; // Skip over the two '{' s
            while(szCurr < szEnd)
            {
                if(szCurr[0] == '}' && szCurr[1] == '}')
                    break;
                else if(szCurr[0] == '{')
                    break;

                LPSTR szNext = CharNextExA(m_nCodePage, szCurr, 0);
                if(szNext == szCurr)
                {
                    // embedded null
                    AddError(IDS_STENCIL_EMBEDDED_NULL, NULL);
                    return true;
                }
                szCurr = szNext;
            }

            if(szCurr >= szEnd)
            {
                AddError(IDS_STENCIL_UNMATCHED_TAG_START, szStart);
                if(AddToken(szStart, szCurr - 1, STENCIL_TEXTTAG) == STENCIL_INVALIDINDEX)
                {
                    AddError(IDS_STENCIL_OUTOFMEMORY, pStart);
                    return false;
                }

                break;
            }

            if(szCurr[0] == '{')
            {
                if(szCurr[1] != '{')
                {
                    szCurr--;
                }
                AddError(IDS_STENCIL_MISMATCHED_TAG_START, szStart);
                if(AddToken(szStart, szCurr - 1, STENCIL_TEXTTAG) == STENCIL_INVALIDINDEX)
                {
                    AddError(IDS_STENCIL_OUTOFMEMORY, pStart);
                    return false;
                }

                continue;
            }

            szEndTag = CharNextExA(m_nCodePage, szCurr, 0);
            if(szEndTag == szCurr)
            {
                // embedded null
                AddError(IDS_STENCIL_EMBEDDED_NULL, NULL);
                return true;
            }

            PARSE_TOKEN_RESULT ret = ParseToken(szStart, szEndTag, BlockStack, &dwTop);

            if(ret == INVALID_TOKEN)
            {
                dwCurrentTokenIndex = AddToken(szStart, szEndTag, STENCIL_TEXTTAG);
                if(dwCurrentTokenIndex == STENCIL_INVALIDINDEX)
                {
                    AddError(IDS_STENCIL_OUTOFMEMORY, pStart);
                    return false;
                }

                szCurr = CharNextExA(m_nCodePage, szEndTag, 0);
                continue;
            }

            szCurr = CharNextExA(m_nCodePage, szEndTag, 0);
            if(szEndTag == szCurr)
            {
                // embedded null
                AddError(IDS_STENCIL_EMBEDDED_NULL, NULL);
                return true;
            }

            if(ret == RESERVED_TOKEN)
            {
                if(szCurr < szEnd  && *szCurr == '\n')
                    szCurr++;
                else if((szCurr + 1 < szEnd && *szCurr == '\r' && *(szCurr + 1) == '\n'))
                    szCurr += 2;
            }
        }

        return true;
    }

    HTTP_CODE GetHandlerAndMethodNames(
        __in LPCSTR pStart,
        __in LPCSTR pEnd,
        __out_ecount_z(nMethodNameLen) LPSTR pszMethodName,
        __in size_t nMethodNameLen,
        __out_ecount_z(nHandlerNameLen) LPSTR pszHandlerName,
        __in size_t nHandlerNameLen) throw()
    {

        ATLASSERT(pStart);
        ATLASSERT(pEnd);
        ATLASSERT(pEnd > pStart);

        if(!pszMethodName || !pszHandlerName || nMethodNameLen < 1 || nHandlerNameLen < 1)
        {
            ATLASSERT(FALSE);
            AddError(IDS_STENCIL_BAD_PARAMETER, pStart);
            return AtlsHttpError(500, ISE_SUBERR_UNEXPECTED);
        }


        *pszMethodName = '\0';
        *pszHandlerName = '\0';
        CHAR szMethodString[ATL_MAX_METHOD_NAME_LEN + ATL_MAX_HANDLER_NAME_LEN+1];
        HTTP_CODE hcErr = HTTP_SUCCESS;
        //
        // copy the method string
        //
        size_t nMethodLen = (pEnd - pStart) + 1;
        if(nMethodLen >= (ATL_MAX_METHOD_NAME_LEN + ATL_MAX_HANDLER_NAME_LEN + 1))
        {
            AddError(IDS_STENCIL_METHODNAME_TOO_LONG, pStart);
            return AtlsHttpError(500, ISE_SUBERR_LONGMETHODNAME);
        }

        Checked::memcpy_s(szMethodString, ATL_MAX_METHOD_NAME_LEN + ATL_MAX_HANDLER_NAME_LEN + 1, pStart, nMethodLen);
        szMethodString[nMethodLen] = '\0';

        //
        // now crack the method string and get the handler
        // id and function name
        //
        LPSTR szParen = strchr(szMethodString, '(');
        LPSTR szDot = strchr(szMethodString, '.');
        if(szDot && (!szParen || (szDot < szParen)))
        {
            *szDot = '\0';
            szDot++;

            // copy method name
            if(strlen(szDot) < nMethodNameLen)
                Checked::strcpy_s(pszMethodName, nMethodNameLen, szDot);
            else
            {
                AddError(IDS_STENCIL_METHODNAME_TOO_LONG, pStart + (szDot - szMethodString));
                hcErr = AtlsHttpError(500, ISE_SUBERR_LONGMETHODNAME);
            }
            // copy handler name
            if(!hcErr)
            {
                if(strlen(szMethodString) < nHandlerNameLen)
                    Checked::strcpy_s(pszHandlerName, nHandlerNameLen, szMethodString);
                else
                {
                    AddError(IDS_STENCIL_HANDLERNAME_TOO_LONG, pStart);
                    hcErr = AtlsHttpError(500, ISE_SUBERR_LONGHANDLERNAME);
                }
            }
        }
        else
        {
            // only a method name so just copy it.
            if(strlen(szMethodString) < nMethodNameLen)
                Checked::strcpy_s(pszMethodName, nMethodNameLen, szMethodString);
            else
            {
                AddError(IDS_STENCIL_METHODNAME_TOO_LONG, pStart);
                hcErr = AtlsHttpError(500, ISE_SUBERR_LONGMETHODNAME);
            }
        }
        return hcErr;
    }

    virtual HTTP_CODE Render(
        ITagReplacer *pReplacer,
        IWriteStream *pWriteStream,
        CStencilState* pState = NULL) const throw(...)
    {
        ATLENSURE(pReplacer != NULL);
        ATLENSURE(pWriteStream != NULL);

        HTTP_CODE hcErrorCode = HTTP_SUCCESS;
        DWORD dwIndex = 0;
        DWORD dwArraySize = GetTokenCount();

        // set up locale info
        CSaveThreadLocale lcidSave;

        if(pState)
        {
            dwIndex = pState->dwIndex;

            // restore the locale if we're restarting rendering
            if(pState->locale != CP_ACP)
                SetThreadLocale(pState->locale);
        }

        pReplacer->SetStream(pWriteStream);
        while(dwIndex < dwArraySize)
        {
            // RenderToken advances dwIndex appropriately for us.
            dwIndex = RenderToken(dwIndex, pReplacer, pWriteStream, &hcErrorCode, pState);

            if(dwIndex == STENCIL_INVALIDINDEX ||
               hcErrorCode != HTTP_SUCCESS)
                break;
        }

        if(IsAsyncStatus(hcErrorCode))
        {
            ATLASSERT(pState != NULL);   // state is required for async
            if(pState)
                pState->dwIndex = dwIndex;
        }
        // lcidSave destructor will restore the locale info in case it was changed

        return hcErrorCode;
    }

    inline BOOL IsValidIndex(DWORD dwIndex) const throw()
    {
        if(dwIndex == STENCIL_INVALIDINDEX)
            return FALSE;
        if(dwIndex < GetTokenCount())
            return TRUE;
        else
            return FALSE;
    }

    virtual DWORD RenderToken(
        DWORD dwIndex,
        ITagReplacer *pReplacer,
        IWriteStream *pWriteStream,
        HTTP_CODE *phcErrorCode,
        CStencilState* pState = NULL) const throw(...)
    {
        ATLENSURE(pReplacer != NULL);
        ATLENSURE(pWriteStream != NULL);

        const StencilToken* pToken = GetToken(dwIndex);
        DWORD dwNextToken = 0;
        HTTP_CODE hcErrorCode = HTTP_SUCCESS;

        if(!pToken)
            return STENCIL_INVALIDINDEX;

        switch(pToken->type)
        {
        case STENCIL_TEXTTAG:
        {
            pWriteStream->WriteStream(pToken->pStart,
                                      (int)((pToken->pEnd - pToken->pStart) + 1), NULL);
            dwNextToken = dwIndex + 1;
        }
        break;
        case STENCIL_ITERATORSTART:
        {
            HTTP_CODE hcErr = STENCIL_SUCCESS;

#ifdef ATL_DEBUG_STENCILS
            // A 'while' token has to at least be followed by an endwhile!
            if(!IsValidIndex(dwIndex + 1))
            {
                // This should have been caught at parse time
                dwNextToken = STENCIL_INVALIDINDEX;
                hcErrorCode = AtlsHttpError(500, ISE_SUBERR_STENCIL_INVALIDINDEX);
                ATLASSERT(FALSE);
                break;
            }

            // End of loop should be valid
            if(!IsValidIndex(pToken->dwLoopIndex))
            {
                // This should have been caught at parse time
                dwNextToken = STENCIL_INVALIDINDEX;
                hcErrorCode = AtlsHttpError(500, ISE_SUBERR_STENCIL_MISMATCHWHILE);
                ATLASSERT(FALSE);
                break;
            }

            if(pToken->dwFnOffset == STENCIL_INVALIDOFFSET)
            {
                // This should have been caught at parse time
                dwNextToken = STENCIL_INVALIDINDEX;
                hcErrorCode = AtlsHttpError(500, ISE_SUBERR_STENCIL_INVALIDFUNCOFFSET);
                ATLASSERT(FALSE);
                break;
            }
#endif // ATL_DEBUG_STENCILS

            DWORD dwLoopIndex = pToken->dwLoopIndex; // points to the end of the loop

            // Call the replacement method
            // if it returns HTTP_SUCCESS, enter the loop
            // if it returns HTTP_S_FALSE, terminate the loop
            hcErr = pReplacer->RenderReplacement(pToken->dwFnOffset,
                                                 pToken->dwObjOffset, pToken->dwMap, (void *) pToken->dwData);

            if(hcErr == HTTP_SUCCESS)
            {
                dwNextToken = dwIndex + 1;
                hcErrorCode = HTTP_SUCCESS;
            }
            else if(hcErr == HTTP_S_FALSE)
            {
                dwNextToken = dwLoopIndex + 1;
                hcErrorCode = HTTP_SUCCESS;
            }
            else
            {
                dwNextToken = STENCIL_INVALIDINDEX;
                hcErrorCode = hcErr;
                break;
            }
        }
        break;
        case STENCIL_REPLACEMENT:
        {
#ifdef ATL_DEBUG_STENCILS
            if(pToken->dwFnOffset == STENCIL_INVALIDOFFSET)
            {
                // This should have been caught at parse time
                ATLASSERT(FALSE);
                dwNextToken = STENCIL_INVALIDINDEX;
                hcErrorCode = AtlsHttpError(500, ISE_SUBERR_STENCIL_INVALIDFUNCOFFSET);
                break;
            }
#endif // ATL_DEBUG_STENCILS

            hcErrorCode = pReplacer->RenderReplacement(pToken->dwFnOffset,
                          pToken->dwObjOffset, pToken->dwMap, (void *)pToken->dwData);

            if(IsAsyncContinueStatus(hcErrorCode))
                dwNextToken = dwIndex; // call the tag again after we get back
            else
            {
                dwNextToken = dwIndex + 1;

                // when returned from a handler, these indicate that the handler is done
                // and that we should move on to the next handler when called back
                if(hcErrorCode == HTTP_SUCCESS_ASYNC_DONE)
                    hcErrorCode = HTTP_SUCCESS_ASYNC;
                else if(hcErrorCode == HTTP_SUCCESS_ASYNC_NOFLUSH_DONE)
                    hcErrorCode = HTTP_SUCCESS_ASYNC_NOFLUSH;
            }
        }
        break;
        case STENCIL_ITERATOREND:
        {
            dwNextToken = pToken->dwLoopIndex;
            hcErrorCode = HTTP_SUCCESS;
            ATLASSERT(GetToken(dwNextToken)->type == STENCIL_ITERATORSTART);
        }
        break;
        case STENCIL_CONDITIONALSTART:
        {
#ifdef ATL_DEBUG_STENCILS
            if(pToken->type == STENCIL_CONDITIONALSTART && pToken->dwFnOffset == STENCIL_INVALIDOFFSET)
            {
                // This should have been caught at parse time
                ATLASSERT(FALSE);
                dwNextToken = STENCIL_INVALIDINDEX;
                hcErrorCode = AtlsHttpError(500, ISE_SUBERR_STENCIL_INVALIDFUNCOFFSET);
                break;
            }

            if(pToken->dwLoopIndex == STENCIL_INVALIDINDEX)
            {
                // This should have been caught at parse time
                ATLASSERT(FALSE);
                dwNextToken = STENCIL_INVALIDINDEX;
                hcErrorCode = AtlsHttpError(500, ISE_SUBERR_STENCIL_MISMATCHIF);
                break;
            }
#endif // ATL_DEBUG_STENCILS

            DWORD dwLoopIndex = pToken->dwLoopIndex; // points to the end of the loop

            HTTP_CODE hcErr;
            // Call the replacement method.
            // If it returns HTTP_SUCCESS, we render everything up to
            //  the end of the conditional.
            // if it returns HTTP_S_FALSE, the condition is not met and we
            //  render the else part if it exists or jump past the endif otherwise
            hcErr = pReplacer->RenderReplacement(pToken->dwFnOffset,
                                                 pToken->dwObjOffset, pToken->dwMap, (void *)pToken->dwData);

            if(hcErr == HTTP_SUCCESS)
            {
                dwNextToken = dwIndex + 1;
                hcErrorCode = HTTP_SUCCESS;
            }
            else if(hcErr == HTTP_S_FALSE)
            {
                dwNextToken = dwLoopIndex + 1;
                hcErrorCode = HTTP_SUCCESS;
            }
            else
            {
                dwNextToken = STENCIL_INVALIDINDEX;
                hcErrorCode = hcErr;
                break;
            }
        }
        break;
        case STENCIL_CONDITIONALELSE:
        {
#ifdef ATL_DEBUG_STENCILS
            if(pToken->dwLoopIndex == STENCIL_INVALIDINDEX)
            {
                // This should have been caught at parse time
                ATLASSERT(FALSE);
                dwNextToken = STENCIL_INVALIDINDEX;
                hcErrorCode = AtlsHttpError(500, ISE_SUBERR_STENCIL_MISMATCHIF);
                break;
            }
#endif // ATL_DEBUG_STENCILS

            dwNextToken = pToken->dwLoopIndex + 1;
            hcErrorCode = HTTP_SUCCESS;
        }
        break;
        case STENCIL_CONDITIONALEND:
        {
            dwNextToken = dwIndex + 1;
            hcErrorCode = HTTP_SUCCESS;
        }
        break;
        case STENCIL_LOCALE:
        {
            if(pState)
            {
                pState->locale = (LCID) pToken->dwData;
            }
            SetThreadLocale((LCID) pToken->dwData);
            dwNextToken = dwIndex + 1;
        }
        break;
        default:
        {
            ATLASSERT(FALSE);
            dwNextToken = STENCIL_INVALIDINDEX;
            hcErrorCode = AtlsHttpError(500, ISE_SUBERR_STENCIL_UNEXPECTEDTYPE);
            break;
        }
        }

        ATLASSERT(dwNextToken != dwIndex || IsAsyncContinueStatus(hcErrorCode));

        if(phcErrorCode)
            *phcErrorCode = hcErrorCode;

        return dwNextToken;
    }

    DWORD GetTokenCount() const throw()
    {
        return (DWORD) m_arrTokens.GetCount();
    }

    const StencilToken* GetToken(DWORD dwIndex) const throw()
    {
        return &(m_arrTokens[dwIndex]);
    }

    StencilToken* GetToken(DWORD dwIndex) throw()
    {
        return &(m_arrTokens[dwIndex]);
    }

    LPCSTR GetBufferStart() const throw()
    {
        return m_pBufferStart;
    }

    LPCSTR GetBufferEnd() const throw()
    {
        return m_pBufferEnd;
    }

    WORD GetCodePage() const throw()
    {
        return m_nCodePage;
    }

    // IMemoryCacheClient
    STDMETHOD(QueryInterface)(REFIID riid, void **ppv)
    {
        if(!ppv)
            return E_POINTER;

        if(InlineIsEqualGUID(riid, __uuidof(IUnknown)) ||
           InlineIsEqualGUID(riid, __uuidof(IMemoryCacheClient)))
        {
            *ppv = static_cast<IMemoryCacheClient*>(this);
            return S_OK;
        }

        *ppv = NULL;
        return E_NOINTERFACE;
    }

    STDMETHOD_(ULONG, AddRef)()
    {
        return 1;
    }

    STDMETHOD_(ULONG, Release)()
    {
        return 1;
    }

    STDMETHOD(Free)(const void *pData)
    {
        if(!pData)
            return E_POINTER;

        ATLASSERT(*((void **) pData) == static_cast<void*>(this));

        delete this;

        return S_OK;
    }
}; // class CStencil

struct StencilIncludeInfo
{
public:
    CHAR m_szQueryString[ATL_URL_MAX_URL_LENGTH+1];
    CHAR m_szFileName[MAX_PATH];
};


class CIncludeServerContext :
    public CComObjectRootEx<CComMultiThreadModel>,
    public CWrappedServerContext
{
public:
    BEGIN_COM_MAP(CIncludeServerContext)
    COM_INTERFACE_ENTRY(IHttpServerContext)
    END_COM_MAP()

    IWriteStream * m_pStream;
    const StencilIncludeInfo * m_pIncludeInfo;

    CIncludeServerContext() throw()
    {
        m_pStream = NULL;
        m_pIncludeInfo = NULL;
    }

    void Initialize(
        IWriteStream *pStream,
        IHttpServerContext* pServerContext,
        const StencilIncludeInfo * pIncludeInfo) throw()
    {
        ATLASSERT(pStream != NULL);
        ATLASSERT(pServerContext != NULL);
        ATLASSERT(pIncludeInfo != NULL);
        m_pStream = pStream;
        m_spParent = pServerContext;
        m_pIncludeInfo = pIncludeInfo;
    }

    void Initialize(CIncludeServerContext *pOtherContext)
    {
        ATLENSURE(pOtherContext != NULL);
        m_pStream = pOtherContext->m_pStream;
        m_spParent = pOtherContext->m_spParent;
        m_pIncludeInfo = pOtherContext->m_pIncludeInfo;
    }

    LPCSTR GetRequestMethod()
    {
        return "GET";
    }

    LPCSTR GetQueryString()
    {
        ATLASSUME(m_pIncludeInfo != NULL);
        return m_pIncludeInfo->m_szQueryString;
    }

    LPCSTR GetPathTranslated()
    {
        ATLASSUME(m_pIncludeInfo != NULL);
        return m_pIncludeInfo->m_szFileName;
    }

    LPCSTR GetScriptPathTranslated()
    {
        ATLASSUME(m_pIncludeInfo != NULL);
        return m_pIncludeInfo->m_szFileName;
    }

    DWORD GetTotalBytes()
    {
        return 0;
    }

    DWORD GetAvailableBytes()
    {
        return 0;
    }

    BYTE *GetAvailableData()
    {
        return NULL;
    }

    LPCSTR GetContentType()
    {
        return 0;
    }

    BOOL WriteClient(void *pvBuffer, DWORD *pdwBytes)
    {
        ATLASSUME(m_pStream != NULL);
        ATLENSURE(pvBuffer != NULL);
        ATLENSURE(pdwBytes != NULL);

        HRESULT hr = S_OK;
        _ATLTRY
        {
            hr = m_pStream->WriteStream((LPCSTR) pvBuffer, *pdwBytes, pdwBytes);
        }
        _ATLCATCHALL()
        {
            hr = E_FAIL;
        }

        return SUCCEEDED(hr);
    }

    BOOL ReadClient(void * /*pvBuffer*/, DWORD * /*pdwSize*/)
    {
        return FALSE;
    }

    BOOL AsyncReadClient(void * /*pvBuffer*/, DWORD * /*pdwSize*/)
    {
        return FALSE;
    }

    BOOL SendRedirectResponse(LPCSTR /*pszRedirectURL*/)
    {
        return FALSE;
    }

    BOOL SendResponseHeader(
        LPCSTR /*pszHeader*/,
        LPCSTR /*pszStatusCode*/,
        BOOL /*fKeepConn*/)
    {
        return TRUE;
    }

    BOOL DoneWithSession(DWORD /*dwHttpStatusCode*/)
    {
        return TRUE;
    }

    BOOL RequestIOCompletion(PFN_HSE_IO_COMPLETION /*pfn*/, DWORD * /*pdwContext*/)
    {
        return FALSE;
    }
}; // class CIncludeServerContext

class CIDServerContext :
    public CComObjectRootEx<CComMultiThreadModel>,
    public CWrappedServerContext
{
public:
    CHttpResponse *m_pResponse;
    CHttpRequest *m_pRequest;

    BEGIN_COM_MAP(CIDServerContext)
    COM_INTERFACE_ENTRY(IHttpServerContext)
    END_COM_MAP()

    CIDServerContext() throw()
        : m_pResponse(NULL), m_pRequest(NULL)
    {
    }

    BOOL Initialize(
        CHttpResponse *pResponse,
        CHttpRequest *pRequest) throw()
    {
        ATLASSERT(pResponse != NULL);
        ATLASSERT(pRequest != NULL);
        m_pResponse = pResponse;
        m_pRequest = pRequest;
        if(!m_pRequest)
        {
            return FALSE;
        }

        HRESULT hr = m_pRequest->GetServerContext(&m_spParent);
        return (SUCCEEDED(hr));
    }

    LPCSTR GetRequestMethod()
    {
        ATLASSUME(m_pRequest != NULL);
        return m_pRequest->GetMethodString();
    }

    LPCSTR GetQueryString()
    {
        ATLASSUME(m_pRequest != NULL);
        return m_pRequest->GetQueryString();
    }

    LPCSTR GetPathInfo()
    {
        ATLASSUME(m_pRequest != NULL);
        return m_pRequest->GetPathInfo();
    }

    LPCSTR GetPathTranslated()
    {
        ATLASSUME(m_pRequest != NULL);
        return m_pRequest->GetPathTranslated();
    }

    DWORD GetTotalBytes()
    {
        ATLASSUME(m_pRequest != NULL);
        return m_pRequest->GetTotalBytes();
    }

    DWORD GetAvailableBytes()
    {
        ATLASSUME(m_pRequest != NULL);
        return m_pRequest->GetAvailableBytes();
    }

    BYTE *GetAvailableData()
    {
        ATLASSUME(m_pRequest != NULL);
        return m_pRequest->GetAvailableData();
    }

    LPCSTR GetContentType()
    {
        ATLASSUME(m_pRequest != NULL);
        return m_pRequest->GetContentType();
    }

    LPCSTR GetScriptPathTranslated()
    {
        ATLASSUME(m_pRequest != NULL);
        return m_pRequest->GetScriptPathTranslated();
    }

    BOOL WriteClient(void *pvBuffer, DWORD *pdwBytes)
    {
        ATLASSUME(m_pResponse != NULL);
        return m_pResponse->WriteLen((LPCSTR)pvBuffer, *pdwBytes);
    }

    BOOL ReadClient(void *pvBuffer, DWORD *pdwSize)
    {
        ATLASSUME(m_pRequest != NULL);
        return m_pRequest->ReadData((LPSTR)pvBuffer, pdwSize);
    }

    BOOL SendRedirectResponse(LPCSTR pszRedirectURL)
    {
        ATLASSUME(m_pResponse != NULL);
        return m_pResponse->Redirect(pszRedirectURL);
    }

    BOOL TransmitFile(
        HANDLE hFile,
        PFN_HSE_IO_COMPLETION pfn,
        void *pContext,
        LPCSTR szStatusCode,
        DWORD dwBytesToWrite,
        DWORD dwOffset,
        void *pvHead,
        DWORD dwHeadLen,
        void *pvTail,
        DWORD dwTailLen,
        DWORD dwFlags)
    {
        ATLASSUME(m_pResponse != NULL);
        ATLASSUME(m_spParent != NULL);

        m_pResponse->Flush();
        return m_spParent->TransmitFile(hFile, pfn, pContext, szStatusCode,
                                        dwBytesToWrite, dwOffset, pvHead, dwHeadLen, pvTail, dwTailLen, dwFlags);
    }

}; // class CIDServerContext

//
// CHtmlStencil
// CHtmlStencil is a specialization of CStencil. CHtmlStencil adds the following
// capabilities to CStencil:
//
// Support for rendering {{include }} tags
// The {{include }}  tags specify another stencil to be included in-place during
// stencil rendering. The {{include }} tag takes a single parameter which is the
// URL of the stencil to include. That URL can optionally include parameters.
// An example:
// {{include mystencil.srf?param1=value1}}
//
// We also grab the handler name and the name of any subhandlers. The syntax for the
// handler specification is:
// {{handler MyDynamicHandler.dll/Default}}
// which would cause the MyDynamicHandler.dll to be loaded. Once loaded, the stencil
// processor will ask for the IReplacementHandler interface of the object named "Default".
//
// Additional handlers can be specified after the default handler.  An example of an
// additional handler would be:
// {{subhandler OtherHandler MyOtherHandler.dll/Default}}
// would cause the MyOtherHandler.dll to be loaded. Once loaded, the stencil processor will
// ask for the IReplacementHandler interface of the object named "Default" and use it in
// processing the stencil anywhere it sees a stencil tag of the form
// {{OtherHandler.RenderReplacement}}

struct CStringPair
{
    typedef CFixedStringT<CStringA, MAX_PATH> PathStrType;
    typedef CFixedStringT < CStringA, ATL_MAX_HANDLER_NAME_LEN + 1 > HdlrNameStrType;
    PathStrType strDllPath;
    HdlrNameStrType strHandlerName;

    CStringPair()throw()
    {
    }

    CStringPair(PathStrType &strDllPath_, HdlrNameStrType &strHandlerName_) throw(...)
        : strDllPath(strDllPath_), strHandlerName(strHandlerName_)
    {
    }

    CStringPair(CStringA &strDllPath_, CStringA &strHandlerName_) throw(...)
        : strDllPath(strDllPath_), strHandlerName(strHandlerName_)
    {
    }
};

class CStringPairElementTraits :
    public CElementTraitsBase< CStringPair >
{
private:

    static ULONG HashStr(ULONG nHash, CStringElementTraits<CStringA>::INARGTYPE str)
    {
        ATLENSURE(str != NULL);
        const CStringA::XCHAR* pch = str;
        while(*pch != 0)
        {
            nHash = (nHash << 5) + nHash + (*pch);
            pch++;
        }

        return(nHash);
    }

public:
    static ULONG Hash(INARGTYPE pair) throw()
    {
        ULONG nHash = HashStr(0, pair.strDllPath);
        return HashStr(nHash, pair.strHandlerName);
    }

    static bool CompareElements(INARGTYPE pair1, INARGTYPE pair2) throw()
    {
        return((pair1.strDllPath == pair2.strDllPath) && (pair1.strHandlerName == pair2.strHandlerName));
    }

    static int CompareElementsOrdered(INARGTYPE pair1, INARGTYPE pair2) throw()
    {
        return(pair1.strDllPath.Compare(pair2.strDllPath));
    }
};

class CHtmlStencil : public CStencil
{
private:

    ATL_NOINLINE HTTP_CODE RenderInclude(
        ITagReplacer *pReplacer,
        const StencilToken *pToken,
        IWriteStream *pWriteStream,
        CStencilState *pState) const
    {
        ATLASSUME(m_spServiceProvider);
        CComPtr<IHttpServerContext> spServerContext;
        CComPtr<IHttpRequestLookup> spLookup;
        if(FAILED(pReplacer->GetContext(__uuidof(IHttpServerContext), (VOID**) &spServerContext)))
        {
            return AtlsHttpError(500, 0);
        }
        if(FAILED(pReplacer->GetContext(__uuidof(IHttpRequestLookup), (VOID**) &spLookup)))
        {
            return AtlsHttpError(500, 0);
        }
        return RenderInclude(m_spServiceProvider, pWriteStream,
                             (StencilIncludeInfo *)pToken->dwData, spServerContext, spLookup,
                             pState);
    }

    ATL_NOINLINE HTTP_CODE NoCachePage(ITagReplacer *pReplacer) const
    {
        CComPtr<IHttpServerContext> spContext;
        HRESULT hr = pReplacer->GetContext(__uuidof(IHttpServerContext), (void **)&spContext);
        if(hr == S_OK && spContext)
        {
            CComQIPtr<IPageCacheControl> spControl;
            spControl = spContext;
            if(spControl)
                spControl->Cache(FALSE);
        }
        return HTTP_SUCCESS;
    }


    // CAllocIncludeAsyncContext is an unsupported implementation detail of RenderInclude
    class CAllocIncludeAsyncContext :
        public CAllocContextBase
    {
    public:
        CAllocIncludeAsyncContext(CIncludeServerContext *pBase) :
            m_pBase(pBase)
        {

        }
        HTTP_CODE Alloc(IHttpServerContext **ppNewContext)
        {
            ATLASSUME(m_pBase);
            if(!ppNewContext)
                return AtlsHttpError(500, ISE_SUBERR_UNEXPECTED);
            *ppNewContext = NULL;
            CComObjectNoLock<CIncludeServerContext>* pNewServerContext = NULL;
            ATLTRY(pNewServerContext = new CComObjectNoLock<CIncludeServerContext>);
            if(pNewServerContext == NULL)
                return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM);
            pNewServerContext->Initialize(m_pBase);
            pNewServerContext->AddRef();
            *ppNewContext = pNewServerContext;
            return HTTP_SUCCESS;
        }

    private:
        CIncludeServerContext *m_pBase;
    };  // CAllocIncludeAsyncContext


    ATL_NOINLINE HTTP_CODE RenderInclude(
        IServiceProvider *pServiceProvider,
        IWriteStream *pWriteStream,
        const StencilIncludeInfo *pIncludeInfo,
        IHttpServerContext *pServerContext,
        IHttpRequestLookup *pLookup,
        CStencilState* pState = NULL) const throw(...)
    {
        CComObjectStackEx<CIncludeServerContext> serverContext;
        serverContext.Initialize(pWriteStream, pServerContext, pIncludeInfo);
        CAllocIncludeAsyncContext AsyncAllocObj(&serverContext);
        return _AtlRenderInclude(static_cast<IHttpServerContext*>(&serverContext),
                                 pIncludeInfo->m_szFileName,
                                 pIncludeInfo->m_szQueryString,
                                 GetCodePage(),
                                 &AsyncAllocObj,
                                 pServiceProvider,
                                 pLookup,
                                 pState);

    }

protected:
    CAtlMap < CStringA, CStringPair,
            CStringElementTraits<CStringA>, CStringPairElementTraits > m_arrExtraHandlers;
    CHAR m_szBaseDir[MAX_PATH];
    CComPtr<IServiceProvider> m_spServiceProvider;
    CComPtr<IIsapiExtension> m_spExtension;
    CComPtr<IStencilCache> m_spStencilCache;
    CComPtr<IDllCache> m_spDllCache;

public:
    typedef CAtlMap < CStringA, CStringPair,
            CStringElementTraits<CStringA>, CStringPairElementTraits > mapType;
    typedef CStencil baseType;

    CHtmlStencil(IAtlMemMgr *pMemMgr = NULL) throw() :
        CStencil(pMemMgr)
    {

    }

    void Initialize(IServiceProvider *pProvider) throw(...)
    {
        ATLENSURE(pProvider);
        if(m_spServiceProvider)
            m_spServiceProvider.Release();

        m_spServiceProvider = pProvider;
        if(!m_spDllCache)
            pProvider->QueryService(__uuidof(IDllCache), __uuidof(IDllCache), (void **) &m_spDllCache);

        if(!m_spExtension)
            pProvider->QueryInterface(__uuidof(IIsapiExtension), (void **) &m_spExtension);
    }

    BOOL GetIncludeInfo(LPCSTR szParamBegin, LPCSTR szParamEnd, StencilIncludeInfo *pInfo) const
    {
        ATLENSURE(szParamBegin != NULL);
        ATLENSURE(szParamEnd != NULL);
        ATLENSURE(pInfo != NULL);

        LPCSTR szQueryBegin = szParamBegin;

        while(*szQueryBegin && *szQueryBegin != '?' && *szQueryBegin != '}')
        {
            LPSTR szNext = CharNextExA(GetCodePage(), szQueryBegin, 0);
            if(szNext == szQueryBegin)
            {
                return FALSE;
            }

            szQueryBegin = szNext;
        }

        CFixedStringT<CStringA, MAX_PATH> strPath;

        _ATLTRY
        {
            DWORD dwPrefixLen = 0;
            if(*szParamBegin == '"')
            {
                szParamBegin++;
            }
            if(!IsFullPathA(szParamBegin))
            {
                if(*szParamBegin != '\\')
                {
                    strPath = m_szBaseDir;
                }
                else
                {
                    LPCSTR szBackslash = strchr(m_szBaseDir, '\\');
                    if(szBackslash)
                    {
#pragma warning(push)
#pragma warning(disable: 6204)
                        /* prefast noise VSW 492749 */
                        strPath.SetString(m_szBaseDir, (int)(szBackslash - m_szBaseDir));
#pragma warning(pop)
                    }
                    else
                    {
                        strPath = m_szBaseDir;
                    }
                }
                dwPrefixLen = strPath.GetLength();
            }

            if(*szQueryBegin == '?')
            {
                size_t nMinus = (*(szQueryBegin - 1) == '"') ? 1 : 0;
                strPath.Append(szParamBegin, (int)(szQueryBegin - szParamBegin - nMinus));
                if((szParamEnd - szQueryBegin) > ATL_URL_MAX_PATH_LENGTH || ((szParamEnd - szQueryBegin) < 0))
                {
                    // query string is too long
                    return FALSE;
                }
                Checked::memcpy_s(pInfo->m_szQueryString, ATL_URL_MAX_URL_LENGTH + 1, szQueryBegin + 1, szParamEnd - szQueryBegin);
                pInfo->m_szQueryString[szParamEnd - szQueryBegin] = '\0';
            }
            else
            {
                pInfo->m_szQueryString[0] = '\0';
                size_t nAdd = (*szParamEnd == '"') ? 0 : 1;
                strPath.Append(szParamBegin, (int)(szParamEnd - szParamBegin + nAdd));
            }
        }
        _ATLCATCHALL()
        {
            // out of memory
            return FALSE;
        }

        if(strPath.GetLength() > MAX_PATH - 1)
        {
            // path is too long
            return FALSE;
        }

        // strPath is <= MAX_PATH-1
        return PathCanonicalizeA(pInfo->m_szFileName, strPath);
    }

    DWORD ParseInclude(LPCSTR szTokenStart, LPCSTR szTokenEnd) throw(...)
    {
        ATLENSURE(szTokenStart != NULL);
        ATLENSURE(szTokenEnd != NULL);

        LPCSTR szStart = szTokenStart;
        LPCSTR szEnd = szTokenEnd;

        FindTagArgs(szStart, szEnd, 7);

        CFixedStringT<CStringA, MAX_PATH> strFileNameRelative;
        CFixedStringT<CString, MAX_PATH> strFileName;

        _ATLTRY
        {
            strFileNameRelative.SetString(szStart, (int)(szEnd - szStart + 1));

            if(!IsFullPathA(strFileNameRelative))
            {
                CFixedStringT<CStringA, MAX_PATH> strTemp;
                if(*((LPCSTR)strFileNameRelative) != '\\')
                {
                    strTemp = m_szBaseDir;
                }
                else
                {
                    LPCSTR szBackslash = strchr(m_szBaseDir, '\\');
                    if(szBackslash)
                    {
#pragma warning(push)
#pragma warning(disable: 6204)
#pragma warning(disable: 6535)
                        /* prefast noise VSW 492749 */
                        /* prefast noise VSW 493256 */
                        strTemp.SetString(m_szBaseDir, (int)(szBackslash - m_szBaseDir));
#pragma warning(pop)
                    }
                    else
                    {
                        strTemp = m_szBaseDir;
                    }
                }

                strTemp.Append(strFileNameRelative, strFileNameRelative.GetLength());
                CFixedStringT<CString, MAX_PATH> strConv = (LPCTSTR) CA2CT(strTemp);
                LPTSTR szFileBuf = strFileName.GetBuffer(strConv.GetLength() + 1);
                if(szFileBuf == NULL)
                {
                    AddError(IDS_STENCIL_OUTOFMEMORY, szTokenStart);
                    return AddToken(szTokenStart, szTokenEnd, STENCIL_TEXTTAG);
                }

                if(!PathCanonicalize(szFileBuf, strConv))
                {
                    return STENCIL_INVALIDINDEX;
                }

                strFileName.ReleaseBuffer();
            }
            else
            {
                strFileName = CA2CTEX<MAX_PATH>(strFileNameRelative);
            }
        }
        _ATLCATCHALL()
        {
            AddError(IDS_STENCIL_OUTOFMEMORY, szTokenStart);
            return AddToken(szTokenStart, szTokenEnd, STENCIL_TEXTTAG);
        }

        LPCTSTR szFileName = strFileName;

        LPCTSTR szDot = NULL;
        LPCTSTR szExtra = _tcschr(szFileName, '?');
        if(!szExtra)
        {
            szExtra = _tcschr(szFileName, '#');
            if(!szExtra)
            {
                szDot = _tcsrchr(szFileName, '.');
            }
        }

        if(szExtra != NULL)
        {
            // there is some extra information
            LPCTSTR szDotTmp = szFileName;
            do
            {
                szDot = szDotTmp;
                szDotTmp = _tcschr(szDotTmp + 1, '.');
            }
            while(szDotTmp && szDotTmp < szExtra);
        }

        if(!szDot || *szDot != '.')
        {
            AddError(IDS_STENCIL_UNEXPECTED, szTokenStart);
            return AddToken(szTokenStart, szTokenEnd, STENCIL_TEXTTAG);
        }

        LPCTSTR szExtEnd = szDot;

        while(true)
        {
            szExtEnd++;
            if(!*szExtEnd || *szExtEnd == '/' || *szExtEnd == '\\' || *szExtEnd == '?' || *szExtEnd == '#' || *szExtEnd == '"')
                break;
        }

        if(szDot && (size_t)(szExtEnd - szDot) == _tcslen(c_tAtlDLLExtension) &&
           !_tcsnicmp(szDot, c_tAtlDLLExtension, _tcslen(c_tAtlDLLExtension)))
        {
            // Do .dll stuff
            DWORD dwIndex = AddToken(szStart, szEnd, STENCIL_STENCILINCLUDE);
            if(dwIndex == STENCIL_INVALIDINDEX)
            {
                AddError(IDS_STENCIL_OUTOFMEMORY, szTokenStart);
                return AddToken(szTokenStart, szTokenEnd, STENCIL_TEXTTAG);
            }
            StencilIncludeInfo *pInfo = (StencilIncludeInfo *)m_pMemMgr->Allocate(sizeof(StencilIncludeInfo));
            if(!pInfo)
            {
                return STENCIL_INVALIDINDEX;
            }

            if(!GetIncludeInfo(szStart, szEnd, pInfo))
            {
                return STENCIL_INVALIDINDEX;
            }

            GetToken(dwIndex)->dwData = (DWORD_PTR) pInfo;
            return dwIndex;
        }
        else if(szDot && (size_t)(szExtEnd - szDot) == _tcslen(c_tAtlSRFExtension) &&
                !_tcsnicmp(szDot, c_tAtlSRFExtension, _tcslen(c_tAtlSRFExtension)))
        {
            // Do .srf stuff
            DWORD dwIndex = AddToken(szStart, szEnd, STENCIL_STENCILINCLUDE);
            if(dwIndex == STENCIL_INVALIDINDEX)
            {
                AddError(IDS_STENCIL_OUTOFMEMORY, szTokenStart);
                return AddToken(szTokenStart, szTokenEnd, STENCIL_TEXTTAG);
            }
            StencilIncludeInfo *pInfo = (StencilIncludeInfo *)m_pMemMgr->Allocate(sizeof(StencilIncludeInfo));
            if(!pInfo)
            {
                return STENCIL_INVALIDINDEX;
            }

            if(!GetIncludeInfo(szStart, szEnd, pInfo))
            {
                return STENCIL_INVALIDINDEX;
            }

            GetToken(dwIndex)->dwData = (DWORD_PTR) pInfo;
            return dwIndex;
        }
        else
        {
            // Assume static content
            CAtlFile file;

            HRESULT hr = file.Create(szFileName, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING);
            if(FAILED(hr) || GetFileType(file) != FILE_TYPE_DISK)
            {
                if(FAILED(hr))
                {
                    AddError(IDS_STENCIL_INCLUDE_ERROR, szTokenStart);
                }
                else
                {
                    AddError(IDS_STENCIL_INCLUDE_INVALID, szTokenStart);
                }
                return AddToken(szTokenStart, szTokenEnd, STENCIL_TEXTTAG);
            }

            CAutoVectorPtr<CHAR> szBufferStart;
            LPSTR szBufferEnd = NULL;
            ULONGLONG dwLen = 0;
            if(FAILED(file.GetSize(dwLen)))
            {
                return AddToken(szTokenStart, szTokenEnd, STENCIL_TEXTTAG);
            }

            if(!szBufferStart.Allocate((size_t) dwLen))
            {
                AddError(IDS_STENCIL_OUTOFMEMORY, szTokenStart);
                return AddToken(szTokenStart, szTokenEnd, STENCIL_TEXTTAG);
            }

            DWORD dwRead;
            if(FAILED(file.Read(szBufferStart, (DWORD) dwLen, dwRead)))
            {
                return AddToken(szTokenStart, szTokenEnd, STENCIL_TEXTTAG);
            }

            szBufferEnd = szBufferStart + dwRead - 1;

            DWORD dwIndex = AddToken(szBufferStart, szBufferEnd, STENCIL_STATICINCLUDE);
            if(dwIndex != STENCIL_INVALIDINDEX)
            {
                GetToken(dwIndex)->bDynamicAlloc = TRUE;
                szBufferStart.Detach();
            }

            return dwIndex;
        }
    }

    PARSE_TOKEN_RESULT ParseSubhandler(LPCSTR szTokenStart, LPCSTR szTokenEnd) throw()
    {
        ATLASSERT(szTokenStart != NULL);
        ATLASSERT(szTokenEnd != NULL);

        LPCSTR szStart = szTokenStart;
        LPCSTR szEnd = szTokenEnd;

        // move to the start of the arguments
        // (the first char past 'subhandler'
        FindTagArgs(szStart, szEnd, 10);

        // skip any space to bring us to the start
        // of the id for the subhandler.
        szStart = SkipSpace(szStart, GetCodePage());

        // id names cannot contain spaces. Mark the
        // beginning and end if the subhandler id
        LPCSTR szIdStart = szStart;
        while(!isspace(static_cast<unsigned char>(*szStart)) && *szStart != '}')
        {
            if(!isalnum(static_cast<unsigned char>(*szStart)))
            {
                // id names can only contain alphanumeric characters
                return INVALID_TOKEN;
            }

            LPSTR szNext = CharNextExA(GetCodePage(), szStart, 0);
            if(szNext == szStart)
            {
                // embedded null
                AddError(IDS_STENCIL_EMBEDDED_NULL, NULL);
                return INVALID_TOKEN;
            }
            szStart = szNext;
        }
        LPCSTR szIdEnd = szStart;

        // skip space to bring us to the beginning of the
        // the dllpath/handlername
        szStart = SkipSpace(szStart, GetCodePage());

        // everything up to the end if the tag is
        // part of the dllpath/handlername
        LPCSTR szHandlerStart = szStart;
        while(*szStart != '}')
        {
            LPCSTR szNext = CharNextExA(GetCodePage(), szStart, 0);
            if(szNext == szStart)
            {
                // embedded null
                AddError(IDS_STENCIL_EMBEDDED_NULL, NULL);
                return INVALID_TOKEN;
            }
            szStart = szNext;
        }
        LPCSTR szHandlerEnd = szStart;

        _ATLTRY
        {
            CStringPair::HdlrNameStrType strName(szIdStart, (int)(szIdEnd - szIdStart));
            CStringPair::PathStrType strPath(szHandlerStart, (int)(szHandlerEnd - szHandlerStart));

            CStringPair::PathStrType strDllPath;
            CStringPair::HdlrNameStrType strHandlerName;
            DWORD dwDllPathLen = MAX_PATH;
            DWORD dwHandlerNameLen = ATL_MAX_HANDLER_NAME_LEN + 1;

            LPSTR szDllPath = strDllPath.GetBuffer(dwDllPathLen);
            LPSTR szHandlerName = strHandlerName.GetBuffer(dwHandlerNameLen);

            if(!_AtlCrackHandler(strPath, szDllPath, &dwDllPathLen, szHandlerName, &dwHandlerNameLen))
            {
                strDllPath.ReleaseBuffer();
                strHandlerName.ReleaseBuffer();
                AddError(IDS_STENCIL_INVALID_SUBHANDLER, szTokenStart);
                return INVALID_TOKEN;
            }

            strDllPath.ReleaseBuffer(dwDllPathLen);
            strHandlerName.ReleaseBuffer(dwHandlerNameLen);

            m_arrExtraHandlers.SetAt(strName, CStringPair(strDllPath, strHandlerName));
        }
        _ATLCATCHALL()
        {
            AddError(IDS_STENCIL_OUTOFMEMORY, NULL);
            return INVALID_TOKEN;
        }
        return RESERVED_TOKEN;
    }

    virtual PARSE_TOKEN_RESULT ParseToken(LPCSTR szTokenStart, LPCSTR szTokenEnd, DWORD *pBlockStack, DWORD *pdwTop)
    {
        ATLASSERT(szTokenStart != NULL);
        ATLASSERT(szTokenEnd != NULL);

        LPCSTR pStart = szTokenStart;
        pStart += 2; //skip curlies
        pStart = SkipSpace(pStart, GetCodePage());
        DWORD dwLen = (DWORD)(szTokenEnd - szTokenStart);

        DWORD dwIndex = STENCIL_INVALIDINDEX;

        if(CheckTag("include", sizeof("include") - 1, pStart, dwLen))
        {
            dwIndex = ParseInclude(szTokenStart, szTokenEnd);
        }
        else if(dwLen > 3 && !memcmp("!--", pStart, 3))
        {
            return RESERVED_TOKEN;
        }
        else if(dwLen > 2 && !memcmp("//", pStart, 2))
        {
            return RESERVED_TOKEN;
        }
        else if(CheckTag("subhandler", sizeof("subhandler") - 1, pStart, dwLen))
        {
            return ParseSubhandler(szTokenStart, szTokenEnd);
        }
        else
        {
            return CStencil::ParseToken(szTokenStart, szTokenEnd, pBlockStack, pdwTop);
        }
        if(dwIndex == STENCIL_INVALIDINDEX)
        {
            return INVALID_TOKEN;
        }
        return RESERVED_TOKEN;
    }

    mapType* GetExtraHandlers() throw()
    {
        return &m_arrExtraHandlers;
    }

    BOOL SetBaseDirFromFile(LPCSTR szBaseDir)
    {
        if(!SafeStringCopy(m_szBaseDir, szBaseDir))
        {
            return FALSE;
        }

        LPSTR szSlash = strrchr(m_szBaseDir, '\\');
        if(szSlash)
        {
            szSlash++;
            *szSlash = '\0';
        }
        else
        {
            *m_szBaseDir = '\0';
        }

        return TRUE;
    }

    LPCSTR GetBaseDir()
    {
        return m_szBaseDir;
    }

    DWORD RenderToken(
        DWORD dwIndex,
        ITagReplacer* pReplacer,
        IWriteStream *pWriteStream,
        HTTP_CODE *phcErrorCode,
        CStencilState* pState = NULL) const throw(...)
    {
        DWORD dwNextToken = STENCIL_INVALIDINDEX;
        HTTP_CODE hcErrorCode = HTTP_SUCCESS;
        const StencilToken* pToken = GetToken(dwIndex);
        if(pToken)
        {
            if(pToken->type == STENCIL_STENCILINCLUDE)
            {
                hcErrorCode = RenderInclude(pReplacer, pToken, pWriteStream, pState);
                if(hcErrorCode == HTTP_SUCCESS || IsAsyncDoneStatus(hcErrorCode))
                {
                    dwNextToken = dwIndex + 1;
                }
                else if(IsAsyncContinueStatus(hcErrorCode))
                {
                    dwNextToken = dwIndex;
                }
            }
            else if(pToken->type == STENCIL_STATICINCLUDE)
            {
                pWriteStream->WriteStream(pToken->pStart,
                                          (int)((pToken->pEnd - pToken->pStart) + 1), NULL);
                dwNextToken = dwIndex + 1;
            }
            else
            {
                dwNextToken = baseType::RenderToken(dwIndex, pReplacer,
                                                    pWriteStream, &hcErrorCode, pState);
            }
        }

        if(hcErrorCode == HTTP_SUCCESS_NO_CACHE)
        {
            hcErrorCode = NoCachePage(pReplacer);
        }

        if(phcErrorCode)
        {
            *phcErrorCode = hcErrorCode;
        }
        return dwNextToken;
    }
}; // class CHtmlStencil


__declspec(selectany) CCRTHeap CStencil::m_crtHeap;

//
// CHtmlTagReplacer
// This class manages CStencil based objects for HTTP requests. This class will retrieve
// CStencil based objects from the stencil cache, store CStencil based objects in the
// stencil cache and allocate and initialize CStencil based objects on a per reqeust
// basis. Typically, one instance of this class is created for each HTTP request. The
// instance is destroyed once the request has been completed.
template < class THandler, class StencilType = CHtmlStencil >
class CHtmlTagReplacer :
    public ITagReplacerImpl<THandler>
{
protected:
    typedef StencilType StencilType;

    CSimpleArray<HINSTANCE> m_hInstHandlers;
    typedef CAtlMap<CStringA, IRequestHandler*, CStringElementTraits<CStringA> > mapType;
    mapType m_Handlers;
    StencilType *m_pLoadedStencil;
    WORD m_nCodePage;
    CComPtr<IStencilCache> m_spStencilCache;

    AtlServerRequest m_RequestInfo;

public:
    // public members

    CHtmlTagReplacer() throw() :
        m_pLoadedStencil(NULL)
    {
        memset(&m_RequestInfo, 0x00, sizeof(m_RequestInfo));
        m_nCodePage = CP_THREAD_ACP;
    }

    ~CHtmlTagReplacer() throw()
    {
        // you should call FreeHandlers before
        // the object is destructed
        ATLASSUME(m_hInstHandlers.GetSize() == 0);
    }

    HTTP_CODE Initialize(AtlServerRequest *pRequestInfo, IHttpServerContext *pSafeSrvCtx = NULL) throw(...)
    {
        ATLASSERT(pRequestInfo != NULL);

        CComPtr<IServiceProvider> spServiceProvider;
        THandler *pT = static_cast<THandler*>(this);
        HRESULT hr = pT->GetContext(__uuidof(IServiceProvider), (void **)&spServiceProvider);
        if(FAILED(hr))
            return HTTP_FAIL;

        spServiceProvider->QueryService(__uuidof(IStencilCache), __uuidof(IStencilCache), (void **) &m_spStencilCache);
        if(!m_spStencilCache)
        {
            ATLASSERT(FALSE);
            return HTTP_FAIL;
        }

        // copy the AtlServerRequest into the safe version
        Checked::memcpy_s(&m_RequestInfo, sizeof(m_RequestInfo), pRequestInfo, sizeof(m_RequestInfo));

        // override appropriate fields
        m_RequestInfo.cbSize = sizeof(m_RequestInfo);
        m_RequestInfo.pServerContext = pSafeSrvCtx;

        return HTTP_SUCCESS;
    }

    HTTP_CODE LoadStencilResource(
        HINSTANCE hInstResource,
        LPCSTR szResourceID,
        LPCSTR szResourceType = NULL, LPCSTR szStencilName = NULL) throw(...)
    {
        if(!szResourceType)
            szResourceType = (LPCSTR)(RT_HTML);
        // look up stencil in cache
        HTTP_CODE hcErr = HTTP_SUCCESS;

        // check the cache first
        StencilType *pStencil = FindCacheStencil(szStencilName ? szStencilName : szResourceID);
        if(!pStencil)
        {
            // create a new stencil
            pStencil = GetNewCacheStencil();
            if(!pStencil)
            {
                return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM);
            }

            THandler *pT = static_cast<THandler*>(this);
            LPCSTR szFileName = pT->m_spServerContext->GetScriptPathTranslated();

            if(!szFileName)
                return HTTP_FAIL;

            if(!pStencil->SetBaseDirFromFile(szFileName))
            {
                return HTTP_FAIL;
            }

            pStencil->SetErrorResource(GetResourceInstance());

            // load the stencil and parse its replacements
            if(HTTP_SUCCESS == pStencil->LoadFromResource(hInstResource,
                    szResourceID, szResourceType))
            {
                _ATLTRY
                {
                    if(!pStencil->ParseReplacements(this))
                    {
                        return AtlsHttpError(500, ISE_SUBERR_BADSRF);
                    }

                    hcErr = FinishLoadStencil(pStencil, NULL);
                    if(!hcErr)
                    {
#ifdef ATL_DEBUG_STENCILS
                        pStencil->FinishParseReplacements();
#else
                        if(!pStencil->FinishParseReplacements())
                        {
                            return AtlsHttpError(500, ISE_SUBERR_BADSRF);
                        }
#endif // ATL_DEBUG_STENCILS
                    }
                }
                _ATLCATCHALL()
                {
                    return HTTP_FAIL;
                }
            }
            else
            {
                hcErr = HTTP_FAIL;
            }

            // if everything went OK, put the stencil in the stencil cache.
            if(!hcErr)
            {
                hcErr = CacheStencil(szStencilName ? szStencilName : szResourceID, pStencil);
            }

            if(pStencil && hcErr)  // something went wrong, free the stencil data
            {
                FreeCacheStencil(pStencil);
            }
        }
        else
        {
            hcErr = FinishLoadStencil(pStencil);
        }

        return hcErr;
    }

    HTTP_CODE LoadStencilResource(HINSTANCE hInstResource, UINT nID, LPCSTR szResourceType = NULL) throw(...)
    {
        if(!szResourceType)
            szResourceType = (LPCSTR) RT_HTML;
        char szName[80];
        int nResult = sprintf_s(szName, sizeof(szName), "%p/%u", hInstResource, nID);
        if((nResult < 0) || (nResult == sizeof(szName)))
        {
            return HTTP_FAIL;
        }
        return LoadStencilResource(hInstResource, MAKEINTRESOURCEA(nID), szResourceType, szName);
    }

    HTTP_CODE LoadStencil(LPCSTR szFileName, IHttpRequestLookup * pLookup = NULL) throw(...)
    {
        if(!szFileName)
        {
            return HTTP_FAIL;
        }

        HTTP_CODE hcErr = HTTP_FAIL;
        // try to find the stencil in the cache
        StencilType *pStencil = FindCacheStencil(szFileName);

        if(!pStencil)
        {
            // not in cache. Create a new one
            pStencil = GetNewCacheStencil();
            if(!pStencil)
            {
                return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM); // out of memory!
            }

            if(!pStencil->SetBaseDirFromFile(szFileName))
            {
                return HTTP_FAIL;
            }

            pStencil->SetErrorResource(GetResourceInstance());

            // finish loading
            hcErr = pStencil->LoadFromFile(szFileName);
            if(!hcErr)
            {
                _ATLTRY
                {
                    if(!pStencil->ParseReplacements(static_cast<ITagReplacer*>(this)))
                    {
                        return AtlsHttpError(500, ISE_SUBERR_BADSRF);
                    }

                    hcErr = FinishLoadStencil(pStencil, pLookup);
                    if(!hcErr)
                    {
#ifdef ATL_DEBUG_STENCILS
                        pStencil->FinishParseReplacements();
#else
                        if(!pStencil->FinishParseReplacements())
                        {
                            return AtlsHttpError(500, ISE_SUBERR_BADSRF);
                        }
#endif // ATL_DEBUG_STENCILS
                    }
                }
                _ATLCATCHALL()
                {
                    return HTTP_FAIL;
                }
            }

            // if everything is OK, cache the stencil
            if(!hcErr)
            {
                hcErr = CacheStencil(szFileName, pStencil);
            }

            if(pStencil && hcErr)  // something went wrong, free stencil data
                FreeCacheStencil(pStencil);
        }
        else
        {
            hcErr = FinishLoadStencil(pStencil, pLookup);
        }
        return hcErr;
    }

    HTTP_CODE RenderStencil(IWriteStream* pStream, CStencilState* pState = NULL) throw(...)
    {
        if(!m_pLoadedStencil)
            return AtlsHttpError(500, ISE_SUBERR_UNEXPECTED);

        WORD nCodePage = m_pLoadedStencil->GetCodePage();
        if(nCodePage != CP_ACP)
            m_nCodePage = nCodePage;

        HTTP_CODE hcErr = HTTP_FAIL;

        hcErr = m_pLoadedStencil->Render(static_cast<ITagReplacer*>(this),
                                         pStream, pState);

        if(!IsAsyncStatus(hcErr) && m_pLoadedStencil->GetCacheItem())
            m_spStencilCache->ReleaseStencil(m_pLoadedStencil->GetCacheItem());

        return hcErr;
    }


//Implementation

    void FreeHandlers() throw(...)
    {
        POSITION pos = m_Handlers.GetStartPosition();
        while(pos)
        {
            m_Handlers.GetValueAt(pos)->UninitializeHandler();
            m_Handlers.GetNextValue(pos)->Release();
        }
        m_Handlers.RemoveAll();

        int nLen = m_hInstHandlers.GetSize();
        if(nLen != 0)
        {
            THandler *pT = static_cast<THandler *>(this);
            CComPtr<IDllCache> spDllCache;
            pT->m_spServiceProvider->QueryService(__uuidof(IDllCache), __uuidof(IDllCache),
                                                  (void **)&spDllCache);
            for(int i = 0; i < nLen; i++)
            {
                spDllCache->Free(m_hInstHandlers[i]);
            }
            m_hInstHandlers.RemoveAll();
        }
    }

    StencilType* GetNewCacheStencil() throw(...)
    {
        StencilType *pStencil = NULL;
        THandler *pT = static_cast<THandler *>(this);
        IAtlMemMgr *pMemMgr;
        if(FAILED(pT->m_spServiceProvider->QueryService(__uuidof(IAtlMemMgr), __uuidof(IAtlMemMgr), (void **)&pMemMgr)))
            pMemMgr = NULL;

        ATLTRY(pStencil = new StencilType(pMemMgr));
        if(pStencil != NULL)
        {
            pStencil->Initialize(pT->m_spServiceProvider);
        }
        return pStencil;
    }

    HTTP_CODE CacheStencil(
        LPCSTR szName,
        StencilType* pStencilData) throw()
    {
        THandler *pT = static_cast<THandler *>(this);
        HRESULT hr = E_FAIL;

        HCACHEITEM hCacheItem = NULL;

        hr = m_spStencilCache->CacheStencil(szName,
                                            pStencilData,
                                            sizeof(StencilType*),
                                            &hCacheItem,
                                            pT->m_hInstHandler,
                                            static_cast<IMemoryCacheClient*>(pStencilData));

        if(hr == S_OK && hCacheItem)
        {
            _ATLTRY
            {
                pStencilData->SetCacheItem(hCacheItem);
            }
            _ATLCATCHALL()
            {
                hr = E_FAIL;
            }
        }

        return (hr == S_OK) ? HTTP_SUCCESS : HTTP_FAIL;
    }

    StencilType *FindCacheStencil(LPCSTR szName) throw()
    {
        if(!szName || !m_spStencilCache)
            return NULL;

        StencilType *pStencilData = NULL;

        HCACHEITEM hStencil;

        if(m_spStencilCache->LookupStencil(szName, &hStencil) != S_OK)
            return NULL;

        m_spStencilCache->GetStencil(hStencil, reinterpret_cast<void **>(&pStencilData));

        return pStencilData;
    }

    void FreeCacheStencil(StencilType* pStencilData)
    {
        ATLASSERT(pStencilData != NULL);

        if(!pStencilData)
        {
            return;
        }

        IMemoryCacheClient *pMemCacheClient = static_cast<IMemoryCacheClient *>(pStencilData);

        if(!pMemCacheClient)
        {
            return;
        }

        _ATLTRY
        {
            pMemCacheClient->Free(pStencilData);
        }
        _ATLCATCHALL()
        {
        }
    }

    HTTP_CODE GetHandlerOffset(LPCSTR szHandlerName, DWORD* pdwOffset)
    {
        if(!pdwOffset)
            return HTTP_FAIL;

        mapType::CPair *p = m_Handlers.Lookup(szHandlerName);
        if(p)
        {
            DWORD dwIndex = 0;
            POSITION pos = m_Handlers.GetStartPosition();
            while(pos)
            {
                const mapType::CPair *p1 = m_Handlers.GetNext(pos);
                if(p1 == p)
                {
                    *pdwOffset = dwIndex;
                    return HTTP_SUCCESS;
                }
                dwIndex++;
            }
            ATLASSERT(FALSE);
        }
        *pdwOffset = 0;
        return HTTP_FAIL;
    }

    HTTP_CODE GetReplacementObject(DWORD dwObjOffset, ITagReplacer **ppReplacer)
    {
        HRESULT hr = E_FAIL;

        POSITION pos = m_Handlers.GetStartPosition();
        for(DWORD dwIndex = 0; dwIndex < dwObjOffset; dwIndex++)
            m_Handlers.GetNext(pos);

        ATLASSERT(pos != NULL);

        IRequestHandler *pHandler = NULL;
        pHandler = m_Handlers.GetValueAt(pos);

        ATLENSURE(pHandler != NULL);

        hr = pHandler->QueryInterface(__uuidof(ITagReplacer), (void**)ppReplacer);

        if(hr != S_OK)
            return HTTP_FAIL;

        return HTTP_SUCCESS;
    }

    // This is where we would actually load any extra request
    // handlers the HTML stencil might have parsed for us.
    HTTP_CODE FinishLoadStencil(StencilType *pStencil, IHttpRequestLookup * pLookup = NULL) throw(...)
    {
        THandler *pT = static_cast<THandler *>(this);
        ATLASSERT(pStencil);
        if(!pStencil)
            return AtlsHttpError(500, ISE_SUBERR_UNEXPECTED); // unexpected condition
        m_pLoadedStencil = pStencil;
        //load extra handlers if there are any
        StencilType::mapType *pExtraHandlersMap =
            pStencil->GetExtraHandlers();

        if(pExtraHandlersMap)
        {
            POSITION pos = pExtraHandlersMap->GetStartPosition();
            CStringA name;
            CStringPair path;
            IRequestHandler *pHandler;
            HINSTANCE hInstHandler;
            while(pos)
            {
                pExtraHandlersMap->GetNextAssoc(pos, name, path);
                pHandler = NULL;
                hInstHandler = NULL;
                HTTP_CODE hcErr = pT->m_spExtension->LoadRequestHandler(path.strDllPath, path.strHandlerName,
                                  pT->m_spServerContext,
                                  &hInstHandler,
                                  &pHandler);
                if(!hcErr)
                {
                    _ATLTRY
                    {
                        //map the name to the pointer to request handler
                        m_Handlers.SetAt(name, pHandler);
                        //store HINSTANCE of handler
                        m_hInstHandlers.Add(hInstHandler);
                    }
                    _ATLCATCHALL()
                    {
                        return HTTP_FAIL;
                    }

                    if(pLookup)
                    {
                        hcErr = pHandler->InitializeChild(&m_RequestInfo, pT->m_spServiceProvider, pLookup);
                        if(hcErr != HTTP_SUCCESS)
                            return hcErr;
                    }

                }
                else
                    return hcErr;
            }
        }
        return HTTP_SUCCESS;
    }
}; // class CHtmlTagReplacer


// CRequestHandlerT
// This is the base class for all user request handlers. This class implements
// the IReplacementHandler interface whose methods will be called to render HTML
// into a stream. The stream will be returned as the HTTP response upon completion
// of the HTTP request.
template <	class THandler,
          class ThreadModel = CComSingleThreadModel,
          class TagReplacerType = CHtmlTagReplacer<THandler>
          >
class CRequestHandlerT :
    public TagReplacerType,
    public CComObjectRootEx<ThreadModel>,
    public IRequestHandlerImpl<THandler>
{
protected:
    CStencilState m_state;
    CComObjectStackEx<CIDServerContext> m_SafeSrvCtx;
    typedef CRequestHandlerT<THandler, ThreadModel, TagReplacerType> _requestHandler;

public:
    BEGIN_COM_MAP(_requestHandler)
    COM_INTERFACE_ENTRY(IRequestHandler)
    COM_INTERFACE_ENTRY(ITagReplacer)
    END_COM_MAP()

    // public CRequestHandlerT members
    CHttpResponse m_HttpResponse;
    CHttpRequest m_HttpRequest;
    ATLSRV_REQUESTTYPE m_dwRequestType;
    AtlServerRequest* m_pRequestInfo;

    CRequestHandlerT() throw()
    {
        m_hInstHandler = NULL;
        m_dwAsyncFlags = 0;
        m_pRequestInfo = NULL;
    }

    ~CRequestHandlerT() throw()
    {
        _ATLTRY
        {
            FreeHandlers(); // free handlers held by CTagReplacer
        }
        _ATLCATCHALL()
        {
        }
    }

    void ClearResponse() throw()
    {
        m_HttpResponse.ClearResponse();
    }
    // Where user initialization should take place
    HTTP_CODE ValidateAndExchange()
    {
        return HTTP_SUCCESS; // continue processing request
    }

    // Where user Uninitialization should take place
    HTTP_CODE Uninitialize(HTTP_CODE hcError)
    {
        return hcError;
    }

    HTTP_CODE InitializeInternal(AtlServerRequest *pRequestInfo, IServiceProvider *pProvider)
    {
        // Initialize our internal references to required services
        m_pRequestInfo = pRequestInfo;
        m_state.pParentInfo = pRequestInfo;
        m_hInstHandler = pRequestInfo->hInstDll;
        m_spServerContext = pRequestInfo->pServerContext;
        m_spServiceProvider = pProvider;
        return HTTP_SUCCESS;
    }

    HTTP_CODE InitializeHandler(
        AtlServerRequest *pRequestInfo,
        IServiceProvider *pProvider)
    {
        HTTP_CODE hcErr = HTTP_FAIL;
        ATLASSERT(pRequestInfo);
        ATLASSERT(pProvider);

        THandler* pT = static_cast<THandler*>(this);
        hcErr = pT->InitializeInternal(pRequestInfo, pProvider);
        if(!hcErr)
        {
            m_HttpResponse.Initialize(m_spServerContext);
            hcErr = pT->CheckValidRequest();
            if(!hcErr)
            {
                hcErr = HTTP_FAIL;
                if(m_HttpRequest.Initialize(m_spServerContext,
                                            pT->MaxFormSize(),
                                            pT->FormFlags()))
                {
                    if(m_SafeSrvCtx.Initialize(&m_HttpResponse, &m_HttpRequest))
                    {
                        hcErr = TagReplacerType::Initialize(pRequestInfo, &m_SafeSrvCtx);
                        if(!hcErr)
                        {
                            hcErr = pT->ValidateAndExchange();
                        }
                    }
                }
            }
        }
        return hcErr;
    }

    HTTP_CODE InitializeChild(
        AtlServerRequest *pRequestInfo,
        IServiceProvider *pProvider,
        IHttpRequestLookup *pRequestLookup)
    {
        ATLASSERT(pRequestInfo);
        ATLASSERT(pProvider);

        THandler *pT = static_cast<THandler*>(this);
        HTTP_CODE hcErr = pT->InitializeInternal(pRequestInfo, pProvider);
        if(hcErr)
            return hcErr;

        if(pRequestLookup)
        {
            // initialize with the pRequestLookup
            if(!m_HttpResponse.Initialize(pRequestLookup))
            {
                return HTTP_FAIL;
            }

            // Initialize with the IHttpServerContext if it exists
            // the only time this is different than the previous call to
            // initialize is if the user passes a different IHttpServerContext
            // in pRequestInfo than the one extracted from pRequestLookup.
            if(m_spServerContext)
            {
                if(!m_HttpResponse.Initialize(m_spServerContext))
                {
                    return HTTP_FAIL;
                }
            }
            hcErr = pT->CheckValidRequest();
            if(hcErr)
            {
                return hcErr;
            }

            // initialize with the pRequestLookup to chain query parameters
            m_HttpRequest.Initialize(pRequestLookup);

            // initialize with the m_spServerContext to get additional query params
            // if they exist.
            if(m_spServerContext)
            {
                m_HttpRequest.Initialize(m_spServerContext);
            }
        }

        m_HttpResponse.SetBufferOutput(false); // child cannot buffer

        // initialize the safe server context
        if(!m_SafeSrvCtx.Initialize(&m_HttpResponse, &m_HttpRequest))
        {
            return HTTP_FAIL;
        }

        hcErr = TagReplacerType::Initialize(pRequestInfo, &m_SafeSrvCtx);
        if(hcErr)
        {
            return hcErr;
        }

        return pT->ValidateAndExchange();
    }

    // HandleRequest is called to perform default processing of HTTP requests. Users
    // can override this function in their derived classes if they need to perform
    // specific initialization prior to processing this request or want to change the
    // way the request is processed.
    HTTP_CODE HandleRequest(
        AtlServerRequest *pRequestInfo,
        IServiceProvider* /*pServiceProvider*/)
    {
        ATLENSURE(pRequestInfo);

        THandler *pT = static_cast<THandler *>(this);
        HTTP_CODE hcErr = HTTP_SUCCESS;

        if(pRequestInfo->dwRequestState == ATLSRV_STATE_BEGIN)
        {
            m_dwRequestType = pRequestInfo->dwRequestType;

            if(pRequestInfo->dwRequestType == ATLSRV_REQUEST_STENCIL)
            {
                LPCSTR szFileName = pRequestInfo->pServerContext->GetScriptPathTranslated();
                hcErr = HTTP_FAIL;
                if(szFileName)
                    hcErr = pT->LoadStencil(szFileName, static_cast<IHttpRequestLookup *>(&m_HttpRequest));
            }
        }
        else if(pRequestInfo->dwRequestState == ATLSRV_STATE_CONTINUE)
            m_HttpResponse.ClearContent();

#ifdef ATL_DEBUG_STENCILS
        if(m_pLoadedStencil && !m_pLoadedStencil->ParseSuccessful())
        {
            // An error or series of errors occurred in parsing the stencil
            _ATLTRY
            {
                m_pLoadedStencil->RenderErrors(static_cast<IWriteStream*>(&m_HttpResponse));
            }
            _ATLCATCHALL()
            {
                return HTTP_FAIL;
            }
        }
#endif

        if(hcErr == HTTP_SUCCESS && m_pLoadedStencil)
        {
            // if anything other than HTTP_SUCCESS is returned during
            // the rendering of replacement tags, we return that value
            // here.
            hcErr = pT->RenderStencil(static_cast<IWriteStream*>(&m_HttpResponse), &m_state);

            if(hcErr == HTTP_SUCCESS && !m_HttpResponse.Flush(TRUE))
                hcErr = HTTP_FAIL;
        }

        if(IsAsyncFlushStatus(hcErr))
        {
            pRequestInfo->pszBuffer = LPCSTR(m_HttpResponse.m_strContent);
            pRequestInfo->dwBufferLen = m_HttpResponse.m_strContent.GetLength();
        }

        if(pRequestInfo->dwRequestState == ATLSRV_STATE_BEGIN || IsAsyncDoneStatus(hcErr))
            return pT->Uninitialize(hcErr);

        else if(!IsAsyncStatus(hcErr))
            m_HttpResponse.ClearContent();

        return hcErr;
    }

    HTTP_CODE ServerTransferRequest(LPCSTR szRequest, bool bContinueAfterTransfer = false,
                                    WORD nCodePage = 0, CStencilState *pState = NULL) throw(...)
    {
        return m_spExtension->TransferRequest(
                   m_pRequestInfo,
                   m_spServiceProvider,
                   static_cast<IWriteStream*>(&m_HttpResponse),
                   static_cast<IHttpRequestLookup*>(&m_HttpRequest),
                   szRequest,
                   nCodePage == 0 ? m_nCodePage : nCodePage,
                   bContinueAfterTransfer,
                   pState);
    }

    inline DWORD MaxFormSize()
    {
        return DEFAULT_MAX_FORM_SIZE;
    }

    inline DWORD FormFlags()
    {
        return ATL_FORM_FLAG_IGNORE_FILES;
    }

    // Override this function to check if the request
    // is valid. This function is called after m_HttpResponse
    // has been initialized, so you can use it if you need
    // to return an error to the client. This is also a
    // good place to initialize any internal class data needed
    // to handle the request. CRequestHandlerT::CheckValidRequest
    // is called after CRequestHandlerT::InitializeInternal is
    // called, so your override of this method will have access to
    // m_pRequestInfo (this request's AtlServerRequest structure),
    // m_hInstHandler (the HINSTANCE of this handler dll),
    // m_spServerContext (the IHttpServerContext interface for this request),
    // m_spServiceProvider (the IServiceProvider interface for this request).
    // You should call CRequestHandlerT::CheckValidRequest in your override
    // if you override this function.
    //
    // Note that m_HttpRequest has not been initialized, so
    // you cannot use it.  This function is intended to
    // do simple checking throught IHttpServerContext to avoid
    // expensive initialization of m_HttpRequest.
    HTTP_CODE CheckValidRequest()
    {
        LPCSTR szMethod = NULL;
        ATLASSUME(m_pRequestInfo);
        szMethod = m_pRequestInfo->pServerContext->GetRequestMethod();
        if(strcmp(szMethod, "GET") && strcmp(szMethod, "POST") && strcmp(szMethod, "HEAD"))
            return HTTP_NOT_IMPLEMENTED;

        return HTTP_SUCCESS;
    }

    HRESULT GetContext(REFIID riid, void** ppv)
    {
        if(!ppv)
            return E_POINTER;
        if(InlineIsEqualGUID(riid, __uuidof(IHttpServerContext)))
        {
            return m_spServerContext.CopyTo((IHttpServerContext **)ppv);
        }
        if(InlineIsEqualGUID(riid, __uuidof(IHttpRequestLookup)))
        {
            *ppv = static_cast<IHttpRequestLookup*>(&m_HttpRequest);
            m_HttpRequest.AddRef();
            return S_OK;
        }
        if(InlineIsEqualGUID(riid, __uuidof(IServiceProvider)))
        {
            *ppv = m_spServiceProvider;
            m_spServiceProvider.p->AddRef();
            return S_OK;
        }
        return E_NOINTERFACE;
    }

    HINSTANCE GetResourceInstance()
    {
        if(m_pRequestInfo != NULL)
        {
            return m_pRequestInfo->hInstDll;
        }

        return NULL;
    }

    template <typename Interface>
    HRESULT GetContext(Interface** ppInterface) throw(...)
    {
        return GetContext(__uuidof(Interface), reinterpret_cast<void**>(ppInterface));
    }
}; // class CRequestHandlerT

} // namespace ATL
#pragma pack(pop)

#pragma warning( pop )

#endif // __ATLSTENCIL_H__
